You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2008/02/13 07:11:28 UTC

svn commit: r627259 [2/3] - in /incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs: applets/ applets/WiWi-INF/ applets/WiWi-INF/extensions/ error/ images/ scripts/ scripts/fckeditor/ scripts/json-rpc/ templates/ templates/default/ templates/...

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-common.js
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-common.js?rev=627259&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-common.js (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-common.js Tue Feb 12 22:11:16 2008
@@ -0,0 +1,1949 @@
+/**
+ ** Javascript routines to support the BrushedTemplate
+ ** Dirk Frederickx
+ ** Nov 06-Mar 07: aligned with MooTools ##
+ ** moo v1.1 selection needed:  
+ **   Core, Class,  Native, Element(ex. Dimensions), Window,
+ **   Effects(ex. Scroll), Drag(Base), Remote, Plugins(Hash.Cookie, Tips, Accordion)
+ **
+ ** 100 Wiki object (page parms, UserPrefs and setting focus) ##
+ ** 110 WikiSlimbox : attachment viewer ##
+ ** 114 Reflection (adds reflection to images) ##
+ ** 120 QuickLinks object ##
+ ** 130 TabbedSection object
+ ** 132 Accordion object ##
+ ** 140 SearchBox object: remember 10 most recent search topics
+ ** 150 Colors, GraphBar object: e.g. used on the findpage
+ ** 160 URL
+ **
+ ** 200 Collapsible list items
+ ** 220 RoundedCorners ffs
+ ** 230 Sortable (clever table-sort) ##
+ ** 240 Table-filter (excel like table filters ##
+ ** 250 Categories: turn wikipage link into AJAXed popup ##
+ ** 260 WikiTips ##
+ ** 270 WikiColumns ##
+ ** 280 ZebraTable: color odd/even row of a table ##
+ ** 290 HighlightWord: refactored
+ ** 295 Typography
+ ** 300 Prettify
+ **/
+
+
+/* extend mootools */
+String.extend({
+	deCamelize: function(){
+		return this.replace(/([a-z])([A-Z])/g,"$1 $2");
+	}
+});
+
+
+// get text of a dhtml node
+function $getText(el) {
+	return el.innerText || el.textContent || '';
+}
+Element.extend({
+	/* wrapper = new Element('div').injectWrapper(node); */
+	injectWrapper: function(el){
+		while( el.firstChild ) this.appendChild( el.firstChild );
+		el.appendChild( this ) ;
+		return this;
+	},
+	visible: function() {
+		var el = this;
+		while($type(el)=='element'){
+			if(el.getStyle('visibility') == 'hidden') return false;
+			if(el.getStyle('display') == 'none' ) return false;
+			el = el.getParent();
+		}
+		return true;
+	},
+	hide: function() {
+		this.style.display = 'none';
+		return this;
+	},
+	show: function() {
+		this.style.display = '';
+		return this;
+	},	
+	toggle: function() {
+		this.visible() ? this.hide() : this.show();  
+		return this;
+	},
+	scrollTo: function(x, y){
+		this.scrollLeft = x;
+		this.scrollTop = y;
+	},
+	/* dimensions.js */
+	getPosition: function(overflown){
+		overflown = overflown || [];
+		var el = this, left = 0, top = 0;
+		do {
+			left += el.offsetLeft || 0;
+			top += el.offsetTop || 0;
+			el = el.offsetParent;
+		} while (el);
+		overflown.each(function(element){
+			left -= element.scrollLeft || 0;
+			top -= element.scrollTop || 0;
+		});
+		return {'x': left, 'y': top};
+	}
+});
+
+var Observer = new Class({
+	initialize: function(el, fn, options){
+		this.options = Object.extend({
+	   	    event: 'keyup',
+			delay: 300
+		}, options || {});
+		this.element = $(el);
+		this.callback = fn;
+		this.timeout = null;
+		this.listener = this.fired.bind(this);
+		this.value = this.element.getValue();
+		this.element.setProperty('autocomplete','off').addEvent(this.options.event, this.listener);
+	},
+	fired: function() {
+		if (this.value == this.element.value) return;
+		this.clear();
+		this.value = this.element.value;
+		this.timeout = this.callback.delay(this.options.delay, null, [this.element]);
+	},
+	clear: function() {
+		this.timeout = $clear(this.timeout);
+	},
+	stop: function() {
+		this.element.removeEvent(this.options.event, this.listener);
+		this.clear();
+	}
+});
+/* Observable class: observe any form element for changes */
+Element.extend({
+	observe: function(fn, options){
+		return new Observer(this, fn, options);
+	}
+});
+
+
+/* I18N Support
+ * LocalizedStrings takes form { "javascript.some.resource.key":"localised resource key {0}" }
+ * Examples:
+ * "javascript.moreInfo".localize();
+ * "javascript.imageInfo".localize(2,4); => expects "Image {0} of {1}"
+ */
+String.extend({
+	localize: function(){
+		var s = LocalizedStrings["javascript."+this], args = arguments;
+		if(!s) return("???" + this + "???");
+		return s.replace(/\{(\d)\}/g, function(m){ 
+			return args[m.charAt(1)] || "???"+m.charAt(1)+"???";
+		});
+	}
+});
+
+/* FIXME parse number anywhere inside a string */
+Number.REparsefloat = new RegExp( "([+-]?\\d+(:?\\.\\d+)?(:?e[-+]?\\d+)?)", "i");
+
+/** TABLE stuff **/
+function $T(el) {
+	var t = $(el); 
+	return (t && t.tBodies[0]) ? $(t.tBodies[0]) : t;
+};
+
+/* FIXME */
+// find first ancestor element with tagName
+function getAncestorByTagName( node, tagName ) {
+	if( !node) return null;
+	if( node.nodeType == 1 && (node.tagName.toLowerCase() == tagName.toLowerCase())){ 
+		return node; 
+	} else { 
+		return getAncestorByTagName( node.parentNode, tagName ); 
+	}
+}
+
+
+/** 100 Wiki functions **/
+var Wiki = {
+
+	JSONid : 10000,
+	DELIM : '\u00A4',
+	init: function(props){
+		Object.extend(Wiki,props || {}); 
+		var h=location.host;
+		this.BasePath = this.BaseUrl.slice(this.BaseUrl.indexOf(h)+h.length,-1);
+		//this.ClientLanguage = navigator.language ? navigator.language : navigator.userLanguage;
+		//this.ClientTimezone = new Date().getTimezoneOffset()/60;
+		this.prefs=new Hash.Cookie('JSPWikiUserPrefs', {path:Wiki.BasePath, duration:20});
+	},
+	getUrl: function(pagename){
+		return this.PageUrl.replace(/%23%24%25/, pagename);
+	},	
+	/* retrieve pagename from any wikipage url format */
+	getPageName: function(url){
+		var s = this.PageUrl.escapeRegExp().replace(/%23%24%25/, '(.+)'),
+			res = url.match(new RegExp(s));
+		return (res ? res[1] : false);
+	},
+	onPageLoad: function(){
+		this.PermissionEdit = ($E('a.edit') !== undefined); //deduct permission level
+		this.url = null;
+		this.parseLocationHash.periodical(500);
+
+		/* plain.jsp,   login.jsp,   prefs/profile, prefs/prefs, find */
+		['editorarea','j_username','loginname','assertedName','query2'].some(function(el){
+			el = $(el);
+			if(el && el.visible()) { el.focus(); return true; }
+			return false;
+		});
+
+		if($('morebutton')) this.replaceMoreBox(); /* visual sugar */		
+	},
+	savePrefs: function(){
+		if($('prefSkin')) this.prefs.set('SkinName', $('prefSkin').getValue());
+		if($('prefTimeZone')) this.prefs.set('TimeZone', $('prefTimeZone').getValue());
+		if($('prefTimeFormat')) this.prefs.set('DateFormat', $('prefTimeFormat').getValue());
+		if($('prefOrientation')) this.prefs.set('orientation', $('prefOrientation').getValue());
+		if($('editor')) this.prefs.set('editor', $('editor').getValue()); 
+		this.prefs.set('FontSize',this.PrefFontSize);
+	},
+
+	changeOrientation: function(){
+		$('wikibody').className = $('prefOrientation').getValue();
+	},
+	replaceMoreBox: function(){
+		var more = $('morebutton'),
+			popup = new Element('ul').inject(more), 
+			hover = popup.effect('opacity', {wait:false}).set(0),
+			select = $('actionsMore'),
+			separator = '';
+
+		$A(select.options).each(function(o){
+			if(o.value == "") return;
+			separator='separator';
+			new Element('a',{'class':o.className,'href':o.value})
+				.setHTML(o.text).inject(new Element('li').inject(popup));
+		});
+		$('moremenu').inject(new Element('li',{'class':separator}).inject(popup));
+		
+		select.getParent().hide();
+		more.show()
+		 	.addEvent('mouseout',(function(){ hover.start(0) }).bind(this))
+			.addEvent('mouseover',(function(){ Wiki.locatemenu(more,popup); hover.start(0.9) }).bind(this));
+	},
+
+	locatemenu: function(base,el){
+		var win = {'x': window.getWidth(), 'y': window.getHeight()},
+			scroll = {'x': window.getScrollLeft(), 'y': window.getScrollTop()},
+			corner = base.getPosition(),
+			offset = {'x': base.offsetWidth-el.offsetWidth, 'y': base.offsetHeight },
+			popup = {'x': el.offsetWidth, 'y': el.offsetHeight},
+			prop = {'x': 'left', 'y': 'top'};
+
+		for (var z in prop){
+			var pos = corner[z] + offset[z]; /*top-left corner of base */
+			if ((pos + popup[z] - scroll[z]) > win[z]) pos = win[z] - popup[z] + scroll[z];
+			el.setStyle(prop[z], pos);
+		};
+	},
+
+	parseLocationHash: function(){
+		if(this.url && this.url == location.href ) return;
+		this.url = location.href;
+		var h = location.hash; 
+		if( h=="" ) return;
+		h = h.replace(/^#/,'');
+
+		var el = $(h);
+
+		while( $type(el) == 'element' ){
+			if( el.hasClass('hidetab') ){
+				TabbedSection.clickTab.apply(el);
+			} else if( el.hasClass('tab') ){
+				/* accordion -- need to find accordion object */
+			} else if( el.hasClass('collapsebody') ){
+				/* collapsible box */
+			} else if(!el.visible() ){
+				//alert('not visible'+el.id);
+				//fixme need to find the correct toggler
+				//el.show(); //eg collapsedBoxes: fixme
+			}
+			el = el.getParent();
+		}
+
+		location = location.href; /* now jump to the #hash */
+	},
+
+	/* SubmitOnce: disable all buttons to avoid double submit */
+	submitOnce: function(form){
+		window.onbeforeunload = null; /* regular exit of this page -- see jspwiki-edit.js */
+		(function(){ 
+		$A(form.elements).each(function(e){
+				if( (/submit|button/i).test(e.type)) e.disabled = true;
+			});
+		}).delay(10);
+		return true;
+	},
+
+	submitUpload: function(form, progress){
+		$('progressbar').setStyle('visibility','visible');
+		this.progressbar =
+		Wiki.jsonrpc.periodical(1000, this, ["progressTracker.getProgress",[progress],function(result){
+			if(!result.code) $('progressbar').getFirst().setStyle('width',result+'%').setHTML(result+'%');
+		}]);
+
+		return Wiki.submitOnce(form);
+	},
+
+	JSONid : 10000,
+	jsonrpc: function(method, params, fn) {	
+		new Ajax( Wiki.JsonUrl, {
+			postBody: Json.toString({"id":Wiki.JSONid++, "method":method, "params":params}), 
+			method: 'post', 
+			onComplete: function(result){ 
+				var r = Json.evaluate(result,true);
+				if(!r) return;
+				if(r.result) fn(r.result);
+				else if(r.error) fn(r.error);
+			}
+		}).request();
+	}	
+}
+
+
+/** 110 WikiSlimbox
+ ** Inspired http://www.digitalia.be/software/slimbox by Christophe Bleys
+ ** Dirk Frederickx, Mar 2007
+ ** 	%%slimbox [...] %%
+ ** 	%%slimbox-img  [some-image.jpg] %%
+ ** 	%%slimbox-ajax [some-page links] %%
+ **/
+var WikiSlimbox = {
+
+	onPageLoad: function(){
+		var i = 0,
+			lnk = new Element('a',{'class':'slimbox'}).setHTML('»');
+			
+		$$('*[class^=slimbox]').each(function(slim){
+			var group = 'lightbox'+ i++,
+				parm = slim.className.split('-')[1] || 'img ajax',
+				filter = [];
+			if(parm.test('img')) filter.extend(['img.inline', 'a.attachment']); 
+			if(parm.test('ajax')) filter.extend(['a.wikipage', 'a.external']); 
+			$ES(filter.join(','),slim).each(function(el){
+				var href = el.src||el.href;
+				var rel = (el.className.test('inline|attachment')) ? 'img' : 'ajax';
+				if((rel=='img') && !href.test('(.bmp|.gif|.png|.jpg|.jpeg)(\\?.*)?$','i')) return;
+				lnk.clone().setProperties({
+					'href':href, 
+					'rel':group+' '+rel,
+					'title':el.alt||el.getText()
+				}).injectBefore(el);
+				if(el.src) el.replaceWith(new Element('a',{
+					'class':'attachment',
+					'href':el.src
+				}).setHTML(el.alt||el.getText()));
+			});
+		});
+		if(i) Lightbox.init();
+		//new Asset.javascript(Wiki.TemplateDir+'scripts/slimbox.js');
+	}
+}
+
+/*
+	Slimbox v1.31 - The ultimate lightweight Lightbox clone
+	by Christophe Beyls (http://www.digitalia.be) - MIT-style license.
+	Inspired by the original Lightbox v2 by Lokesh Dhakar.
+
+	Updated by Dirk Frederickx to fit JSPWiki needs
+	- minimum size of image canvas DONE
+	- add maximum size of image w.r.t window size DONE
+	- CLOSE icon -> close x text iso icon DONE
+	- <<prev, next>> links added in visible part of screen DONE
+	- add size of picture to info window DONE
+	- spacebor, down arrow, enter : next image DONE
+	- up arrow : prev image DONE
+	- allow the same picture occuring several times DONE
+	- add support for external page links  => slimbox_ex DONE
+*/
+var Lightbox = {
+
+	init: function(options){
+		this.options = $extend({
+			resizeDuration: 400,
+			resizeTransition: false, /*Fx.Transitions.sineInOut,*/
+			initialWidth: 250,
+			initialHeight: 250,
+			animateCaption: true,
+			errorMessage: "slimbox.error".localize()
+		}, options || {});
+
+		this.anchors=[];
+		$each(document.links, function(el){
+			if (el.rel && el.rel.test(/^lightbox/i)){
+				el.onclick = this.click.pass(el, this);
+				this.anchors.push(el);
+			}
+		}, this);
+		this.eventKeyDown = this.keyboardListener.bindAsEventListener(this);
+		this.eventPosition = this.position.bind(this);
+
+		/*	Build float panel
+			<div id="lbOverlay"></div>
+			<div id="lbCenter">
+				<div id="lbImage">
+					<!-- img or iframe element is inserted here -->
+				</div>
+			</div>
+			<div id="lbBottomContainer">
+				<div id="lbBottom">
+					<div id="lbCaption">
+					<div id="lbNumber">
+					<a id="lbCloseLink"></a>
+					<div style="clear:both;"></div>
+				</div>
+			</div>
+		*/
+		this.overlay = new Element('div', {'id': 'lbOverlay'}).inject(document.body);
+
+		this.center = new Element('div', {'id': 'lbCenter', 'styles': {'width': this.options.initialWidth, 'height': this.options.initialHeight, 'marginLeft': -(this.options.initialWidth/2), 'display': 'none'}}).inject(document.body);
+		new Element('a', {'id': 'lbCloseLink', 'href':'#', 'title':'slimbox.close.title'.localize()}).inject(this.center).onclick = this.overlay.onclick = this.close.bind(this);
+		this.image = new Element('div', {'id': 'lbImage'}).inject(this.center);
+
+		this.bottomContainer = new Element('div', {'id': 'lbBottomContainer', 'styles': {'display': 'none'}}).inject(document.body);
+		this.bottom = new Element('div', {'id': 'lbBottom'}).inject(this.bottomContainer);
+		//new Element('a', {'id': 'lbCloseLink', 'href': '#', 'title':'slimbox.close.title'.localize()}).setHTML('slimbox.close'.localize()).inject(this.bottom).onclick = this.overlay.onclick = this.close.bind(this);
+		this.caption = new Element('div', {'id': 'lbCaption'}).inject(this.bottom);
+
+		var info = new Element('div').inject(this.bottom);  
+		this.prevLink = new Element('a', {'id': 'lbPrevLink', 'href': '#', 'styles': {'display': 'none'}}).setHTML("slimbox.previous".localize()).inject(info);
+		this.number = new Element('span', {'id': 'lbNumber'}).inject(info);
+		this.nextLink = this.prevLink.clone().setProperties({'id': 'lbNextLink' }).setHTML("slimbox.next".localize()).inject(info);
+		this.prevLink.onclick = this.previous.bind(this);
+		this.nextLink.onclick = this.next.bind(this);
+
+ 		this.error = new Element('div').setProperty('id', 'lbError').setHTML(this.options.errorMessage);
+		new Element('div', {'styles': {'clear': 'both'}}).inject(this.bottom);
+		
+		var nextEffect = this.nextEffect.bind(this);
+		this.fx = {
+			overlay: this.overlay.effect('opacity', {duration: 500}).hide(),
+			resize: this.center.effects($extend({duration: this.options.resizeDuration, onComplete: nextEffect}, this.options.resizeTransition ? {transition: this.options.resizeTransition} : {})),
+			image: this.image.effect('opacity', {duration: 500, onComplete: nextEffect}),
+			bottom: this.bottom.effect('margin-top', {duration: 400, onComplete: nextEffect})
+		};
+
+		this.fxs = new Fx.Elements([this.center, this.image], $extend({duration: this.options.resizeDuration, onComplete: nextEffect}, this.options.resizeTransition ? {transition: this.options.resizeTransition} : {}));
+		
+		this.preloadPrev = new Image();
+		this.preloadNext = new Image();
+	},
+
+	click: function(link){
+		var rel = link.rel.split(' ');
+		if (rel[0].length == 8) return this.open([[url, title, rel[1]]], 0);
+
+		var imageNum=0, images = [];
+		this.anchors.each(function(el){
+			var elRel = el.rel.split(' ');
+			if (elRel[0]!=rel[0]) return;
+			if((el.href==link.href) && (el.title==link.title)) imageNum = images.length;
+			images.push([el.href, el.title, elRel[1]]);
+		});
+		return this.open(images, imageNum);
+	},
+
+	open: function(images, imageNum){
+		this.images = images;
+		this.position();
+		this.setup(true);
+		this.top = window.getScrollTop() + (window.getHeight() / 15);
+		this.center.setStyles({top: this.top, display: ''});
+		this.fx.overlay.start(0.7);
+		return this.changeImage(imageNum);
+	},
+
+	position: function(){
+		this.overlay.setStyles({top: window.getScrollTop(), height: window.getHeight()});
+	},
+
+	setup: function(open){
+		var elements = $A(document.getElementsByTagName('object'));
+		elements.extend(document.getElementsByTagName(window.ie ? 'select' : 'embed'));
+		elements.each(function(el){
+			if (open) el.lbBackupStyle = el.style.visibility;
+			el.style.visibility = open ? 'hidden' : el.lbBackupStyle;
+		});
+		var fn = open ? 'addEvent' : 'removeEvent';
+		window[fn]('scroll', this.eventPosition)[fn]('resize', this.eventPosition);
+		document[fn]('keydown', this.eventKeyDown);
+		this.step = 0;
+	},
+
+	keyboardListener: function(event){
+		switch (event.keyCode){
+			case 27: case 88: case 67: this.close(); break;
+			case 37: case 38: case 80: this.previous(); break;	
+			case 13: case 32: case 39: case 40: case 78: this.next(); break;
+			default: return;
+		}
+		new Event(event).stop();
+	},
+
+	previous: function(){
+		return this.changeImage(this.activeImage-1);
+	},
+
+	next: function(){
+		return this.changeImage(this.activeImage+1);
+	},
+
+	changeImage: function(imageNum){
+		if (this.step || (imageNum < 0) || (imageNum >= this.images.length)) return false;
+		this.step = 1;
+		this.activeImage = imageNum;
+
+		this.center.style.backgroundColor = '';
+		this.bottomContainer.style.display = this.prevLink.style.display = this.nextLink.style.display = 'none';
+		this.fx.image.hide();
+		this.center.className = 'lbLoading';
+
+		this.preload = new Image();
+		this.image.empty().setStyle('overflow','hidden');
+		if( this.images[imageNum][2] == 'img' ){
+			this.preload.onload = this.nextEffect.bind(this);
+			this.preload.src = this.images[imageNum][0];
+		} else {			
+			this.iframeId = "lbFrame_"+new Date().getTime();	// Safari would not update iframe content that has static id.
+			this.so = new Element('iframe').setProperties({
+				id: this.iframeId, 
+//				width: this.contentsWidth, 
+//				height: this.contentsHeight, 
+				frameBorder:0, 
+				scrolling:'auto', 
+				src:this.images[imageNum][0]
+			}).inject(this.image);
+			this.nextEffect();	//asynchronous loading?
+
+		}
+		return false;
+	},
+
+	ajaxFailure: function (){
+		this.ajaxFailed = true;
+		this.image.setHTML('').adopt(this.error.clone());
+		this.nextEffect();
+	},
+	
+	nextEffect: function(){
+		switch (this.step++){
+		case 1:
+			this.center.className = '';
+			this.caption.empty().adopt(new Element('a', {
+					'href':this.images[this.activeImage][0],
+					'title':"slimbox.directLink".localize()
+				}).setHTML(this.images[this.activeImage][1] || ''));
+				
+			var type = (this.images[this.activeImage][2]=='img') ? "slimbox.info" : "slimbox.remoteRequest";
+			this.number.setHTML((this.images.length == 1) ? '' : type.localize(this.activeImage+1, this.images.length));
+			this.image.style.backgroundImage = 'none';
+
+			var w = Math.max(this.options.initialWidth,this.preload.width),
+				h = Math.max(this.options.initialHeight,this.preload.height),
+				ww = Window.getWidth()-10,
+				wh = Window.getHeight()-120;
+			if(this.images[this.activeImage][2]!='img' &&!this.ajaxFailed){ w = 6000; h = 3000; }
+			if(w > ww) { h = Math.round(h * ww/w); w = ww; }
+			if(h > wh) { w = Math.round(w * wh/h); h = wh; }
+
+			this.image.style.width = this.bottom.style.width = w+'px';
+			this.image.style.height = /*this.prevLink.style.height = this.nextLink.style.height = */ h+'px';
+			
+			if( this.images[this.activeImage][2]=='img') {
+				this.image.style.backgroundImage = 'url('+this.images[this.activeImage][0]+')';
+
+				if (this.activeImage) this.preloadPrev.src = this.images[this.activeImage-1][0];
+				if (this.activeImage != (this.images.length - 1)) this.preloadNext.src = this.images[this.activeImage+1][0];
+			
+				this.number.setHTML(this.number.innerHTML+'&nbsp;&nbsp;['+this.preload.width+'&#215;'+this.preload.height+']');
+			} else {
+				this.so.style.width=w+'px';
+				this.so.style.height=h+'px';
+			}
+
+			if (this.options.animateCaption) this.bottomContainer.setStyles({height: '0px', display: ''});
+
+			this.fxs.start({
+				'0': { height: [this.image.offsetHeight], width: [this.image.offsetWidth], marginLeft: [-this.image.offsetWidth/2] },
+				'1': { opacity: [1] }
+			});	
+
+			break;
+		case 2:
+			//this.center.style.backgroundColor = '#000';
+			this.image.setStyle('overflow','auto');
+			this.bottomContainer.setStyles({ top: (this.top + this.center.clientHeight)+'px', marginLeft: this.center.style.marginLeft });
+			if (this.options.animateCaption){
+				this.fx.bottom.set(-this.bottom.offsetHeight);
+				this.bottomContainer.style.height = '';
+				this.fx.bottom.start(0);
+				break;
+			}
+			this.bottomContainer.style.height = '';
+		case 3:
+			if (this.activeImage) this.prevLink.style.display = '';
+			if (this.activeImage != (this.images.length - 1)) this.nextLink.style.display = '';
+			this.step = 0;
+		}
+	},
+
+	close: function(){
+		if (this.step < 0) return;
+		this.step = -1;
+		if (this.preload){
+			this.preload.onload = Class.empty;
+			this.preload = null;
+		}
+		for (var f in this.fx) this.fx[f].stop();
+		this.center.style.display = this.bottomContainer.style.display = 'none';
+		this.fx.overlay.chain(this.setup.pass(false, this)).start(0);
+		return false;
+	}
+};
+
+
+/** 114 Reflection
+ ** Inspired by Reflection.js at http://cow.neondragon.net/stuff/reflection/
+ ** Freely distributable under MIT-style license.
+ ** Adapted for JSPWiki/BrushedTemplate, D.Frederickx, Sep 06
+ ** Use:
+ ** 	%%reflection-height-opacity  [some-image.jpg] %%
+ **/
+var WikiReflection = {
+
+	onPageLoad: function(){
+		$$('*[class^=reflection]').each( function(w){
+			var parms = w.className.split('-');
+			$ES('img',w).each(function(img){
+				Reflection.add(img, parms[1], parms[2]);
+			}); 
+		});
+	}
+}
+/* FIXME : add delayed loading of reflection library */
+var Reflection = {
+
+	options: { height: 0.33, opacity: 0.5 },
+
+	add: function(img, height, opacity) {
+		//TODO Reflection.remove(image); --is this still needed?
+		height  = (height ) ? height/100 : this.options.height;
+		opacity = (opacity) ? opacity/100: this.options.opacity;
+
+		var div = new Element('div').injectAfter(img).adopt(img),
+			imgW = img.width,
+			imgH = img.height,
+			rH   = Math.floor(imgH * height); //reflection height
+
+		div.className = img.className.replace(/\breflection\b/, "");
+		div.style.cssText = img.backupStyle = img.style.cssText;
+		//div.setStyles({'width':img.width, 'height':imgH +rH, "maxWidth": imgW });
+		div.setStyles({'width':img.width, 'height':imgH +rH });
+		img.style.cssText = 'vertical-align: bottom';
+		//img.className = 'inline reflected';  //FIXME: is this still needed ??
+
+		if( window.ie ){ 
+			new Element('img', {'src': img.src, 'styles': {
+				'width': imgW,
+				'marginBottom': "-" + (imgH - rH) + 'px',
+				'filter': 'flipv progid:DXImageTransform.Microsoft.Alpha(opacity='+(opacity*100)+', style=1, finishOpacity=0, startx=0, starty=0, finishx=0, finishy='+(height*100)+')'
+			}}).inject(div);
+		} else {
+			var r = new Element('canvas', {'width':imgW, 'height':rH, 'styles': {'width':imgW, 'height': rH}}).inject(div);
+			if( !r.getContext ) return;
+
+			var ctx = r.getContext("2d");
+			ctx.save();
+			ctx.translate(0, imgH-1);
+			ctx.scale(1, -1);
+			ctx.drawImage(img, 0, 0, imgW, imgH);
+			ctx.restore();
+			ctx.globalCompositeOperation = "destination-out";
+
+			var g = ctx.createLinearGradient(0, 0, 0, rH);
+			g.addColorStop( 0, "rgba(255, 255, 255, " + (1 - opacity) + ")" );
+			g.addColorStop( 1, "rgba(255, 255, 255, 1.0)" );
+			ctx.fillStyle = g;
+			ctx.rect( 0, 0, imgW, rH );
+			ctx.fill(); 
+		}
+	}
+}
+ 
+/** 120 brushed quick links **/
+var QuickLinks = {
+
+	onPageLoad: function(){
+		if( $("previewcontent") || !Wiki.PrefShowQuickLinks ) return;
+
+		var sections = $$('#pagecontent *[id^=section]');
+		if(sections.length==0) return;
+		
+		var ql,qlPrev,qlEdit,qlNext;
+		function qql(clazz,title,text,href){
+			var a = new Element('a',{'title':title.localize()}).setHTML(text);
+			if(href) a.setProperty('href',href);
+			ql.adopt(new Element('span',{'class':clazz}).adopt(a));
+			return(a);
+		}
+		ql = new Element('div',{'class':'quicklinks'});
+		qql('quick2Top','quick.top','&laquo;','#wikibody');
+		qlPrev = qql('quick2Prev','quick.previous','&lsaquo;');
+
+		if(Wiki.PermissioneEdit) qlEdit = qql('quick2Edit','quick.edit','&bull;');
+
+		qlNext = qql('quick2Next','quick.next','&rsaquo;');
+		qql('quick2Bottom','quick.bottom','&raquo;','#footer');
+				
+		var last = sections.length-1;
+		sections.each(function(s,i){
+			qlNext.setProperty('href',(i==last) ? '#footer' : '#'+sections[i+1].id);
+			qlPrev.setProperty('href',(i==0) ? '#wikibody' : '#'+sections[i-1].id);
+			if(qlEdit) qlEdit.setProperty('href','Edit.jsp?page='+Wiki.PageName+'&section='+(i+1));
+			s.adopt(ql.clone());
+		});
+	}
+}
+
+/** Class: Tabbed Section (130)
+	Creates tabs, based on some css-class information
+	Use in jspwiki: %%tabbedSection  %%tab-FirstTab .. %% %%
+
+	Following markup:
+	<div class="tabbedSection">
+		<div class="tab-FirstTab">..<div>
+		<div class="tab-SecondTab">..<div>
+	</div>
+
+	is changed into
+	<div class="tabmenu"><span><a activetab>..</a></span>..</div>
+	<div class="tabbedSection tabs">
+		<div class="tab-firstTab ">
+		<div class="tab-SecondTab hidetab">
+	</div>
+ **/
+var TabbedSection = {
+
+	onPageLoad: function(){
+
+		// charge existing tabmenu's with click handlers
+		$$('.tabmenu a').each(function(el){
+			if(el.href) return;
+			var tab = $(el.id.substr(5)); //drop 'menu-' prefix
+			el.addEvent('click', this.clickTab.bind(tab) );
+		},this);	
+	
+		// convert tabbedSections into tabmenu's with click handlers
+		$$('.tabbedSection').each( function(tt){
+			tt.addClass('tabs'); //css styling is on tabs
+			var tabmenu = new Element('div',{'class':'tabmenu'}).injectBefore(tt);
+
+			tt.getChildren().each(function(tab,i) {
+				if( !tab.className.test('^tab-') ) return;
+
+				if( !tab.id || (tab.id=="") ) tab.id = tab.className;
+				var title = tab.className.substr(4).deCamelize(); //drop 'tab-' prefix
+
+				(i==0) ? tab.removeClass('hidetab') : tab.addClass('hidetab');
+
+				new Element('div',{'styles':{'clear':'both'}}).inject(tab);
+
+				var menu = new Element('a', {
+					'id':'menu-'+tab.id, 
+					'events':{ 'click': this.clickTab.bind(tab)  }
+				}).appendText(title).inject(tabmenu);
+				if( i==0 ) menu.addClass('activetab');        
+			},this);
+		}, this);
+	},
+
+	clickTab: function(){
+		var menu = $('menu-'+this.id);
+		this.getParent().getChildren().some( function(el){
+			if(el.id){
+				var m = $('menu-'+el.id);
+				if( m && m.hasClass('activetab') ) {
+					if( el.id != this.id ) {
+						m.removeClass('activetab');      
+						menu.addClass('activetab');
+						el.addClass('hidetab');
+						this.removeClass('hidetab'); //.show();
+					}
+					return true;
+				}
+			}
+			return false;
+		},this);		
+	}
+	
+}
+
+/** 132 Accordion for Tabs, Accordeons, CollapseBoxes
+ **
+ ** Following markup:
+ ** <div class="accordion">
+ **		<div class="tab-FirstTab">...<div>
+ **		<div class="tab-SecondTab">...<div>
+ ** </div>
+ **
+ **	is changed into
+ **	<div class="accordion">
+ **		<div class="toggle active">First Tab</div>
+ **		<div class="tab-FirstTab tab active">...</div>
+ **		<div class="toggle">Second Tab</div>
+ **		<div class="tab-SecondTab">...</div>
+ **	</div>
+ **/
+var WikiAccordion = {
+
+	onPageLoad: function(){
+		$$('.accordion, .tabbedAccordion').each( function(tt){
+			
+			var toggles=[], contents=[], togglemenu=false;
+			if(tt.hasClass('tabbedAccordion')) togglemenu = new Element('div',{'class':'togglemenu'}).injectBefore(tt);
+			
+			tt.getChildren().each(function(tab) {
+				if( !tab.className.test('^tab-') ) return;
+
+				//FIXME use class to make tabs visible during printing 
+				//(i==0) ? tab.removeClass('hidetab'): tab.addClass('hidetab');
+
+				var title = tab.className.substr(4).deCamelize();
+				if(togglemenu) {
+					toggles.push(new Element('div',{'class':'toggle'}).inject(togglemenu).appendText(title));
+				} else {
+					toggles.push(new Element('div',{'class':'toggle'}).injectBefore(tab).appendText(title));
+				}        
+				contents.push(tab.addClass('tab'));
+			});
+			new Accordion(toggles, contents, {     
+				alwaysHide: !togglemenu,
+				onComplete: function(){
+					var el = $(this.elements[this.previous]);
+					if (el.offsetHeight > 0) el.setStyle('height', 'auto');  
+				},
+				onActive: function(toggle,content){                          
+					toggle.addClass('active'); content.addClass('active'); 
+				},
+				onBackground: function(toggle,content){ 
+					content.setStyle('height', content['offsetHeight']);
+					toggle.removeClass('active'); content.removeClass('active');
+				} 
+			});
+		});
+	}
+}
+
+
+/* 140 SearchBox
+ * FIXME: remember 10 most recent search topics (cookie based)
+ * Extended with quick links for view, edit and clone (ref. idea of Ron Howard - Nov 05)
+ * Refactored for mootools, April 07
+ */
+var SearchBox = {
+
+	onPageLoad: function(){
+		this.onPageLoadQuickSearch();
+		this.onPageLoadFullSearch();
+	},
+
+	onPageLoadQuickSearch : function(){
+		var q = $('query'); if( !q ) return;
+		this.query = q; 
+		q.observe(this.ajaxQuickSearch.bind(this) ); 
+
+		this.hover = $('searchboxMenu').setProperty('visibility','visible')
+			.effect('opacity', {wait:false}).set(0);
+	
+		$(q.form).addEvent('submit',this.submit.bind(this))
+			//FIXME .addEvent('blur',function(){ this.hasfocus=false; this.hover.start(0) }.bind(this))
+			//FIXME .addEvent('focus',function(){ this.hasfocus=true; this.hover.start(0.9) }.bind(this))
+			  .addEvent('mouseout',function(){ this.hover.start(0) }.bind(this))
+			  .addEvent('mouseover',function(){ Wiki.locatemenu(this.query, $('searchboxMenu') ); this.hover.start(0.9) }.bind(this));
+		
+		/* use advanced search-input on safari - experimental */
+		if(window.xwebkit){
+			q.setProperties({type:"search",autosave:q.form.action,results:"9",placeholder:q.defaultValue});
+		} else {
+			$('recentClear').addEvent('click', this.clear.bind(this));
+
+			this.recent = Wiki.prefs.get('RecentSearch'); if(!this.recent) return;
+
+			var ul = new Element('ul',{'id':'recentItems'}).inject($('recentSearches').show());
+			this.recent.each(function(el){
+				new Element('a',{
+					'href':'#', 
+					'events': {'click':function(){ q.value = el; q.form.submit(); }}
+					}).setHTML(el).inject( new Element('li').inject(ul) );
+			});
+		}
+	},
+
+	onPageLoadFullSearch : function(){
+		var q2 = $("query2"); if( !q2 ) return;
+		this.query2 = q2;
+		
+		var changescope = function(){
+			var qq = this.query2.value.replace(/^(?:author:|name:|contents:|attachment:)/,'');
+			this.query2.value = $('scope').getValue() + qq;
+			this.runfullsearch();
+		}.bind(this);
+		
+		q2.observe( this.runfullsearch.bind(this) );
+		
+		$('scope').addEvent('change', changescope);
+		$('details').addEvent('click', this.runfullsearch.bind(this));
+	},
+
+	runfullsearch : function(){
+		var q2 = this.query2.value;
+		if( !q2 || (q2.trim()=='')) { 
+			$('searchResult2').empty();
+			return; 
+		}
+		$('spin').show();
+
+		var scope = $('scope'), 
+			match= q2.match(/^(?:author:|name:|contents:|attachment:)/) ||"";
+		$each(scope.options, function(option){
+			if (option.value == match) option.selected = true;
+		});
+
+		new Ajax(Wiki.TemplateDir+'AJAXSearch.jsp', {
+			postBody: $('searchform2').toQueryString(),
+			update: 'searchResult2', 
+			method: 'post',
+			onComplete: function() { $('spin').hide(); GraphBar.onPageLoad(); Wiki.prefs.set('PrevQuery', q2); } 
+		}).request();
+	},
+
+	submit: function(){ 
+		var v = this.query.value;
+		if( v == this.query.defaultValue) this.query.value = '';
+		if( !this.recent ) this.recent=[];
+		if( !this.recent.test(v) ){
+			if(this.recent.length > 9) this.recent.pop();
+			this.recent.unshift(v);
+			Wiki.prefs.set('RecentSearch', this.recent);
+		}
+	},
+
+	clear: function(){		
+		this.recent = [];
+		Wiki.prefs.remove('RecentSearch');
+		$('recentSearches','recentClear').hide();
+	},
+
+	ajaxQuickSearch: function(){
+		var qv = this.query.value ;
+		if( (qv==null) || (qv.trim()=="") || (qv==this.query.defaultValue) ) {
+			$('searchOutput').empty();
+			return;
+		}
+		$('searchTarget').setHTML('('+qv+') :');
+		$('searchSpin').show();
+
+		Wiki.jsonrpc('search.findPages', [qv,20], function(result){
+				$('searchSpin').hide(); 
+				if(!result.list) return;
+				var frag = new Element('ul');
+				
+				result.list.each(function(el){
+					new Element('li').adopt( 
+						new Element('a',{'href':Wiki.getUrl(el.map.page) }).setHTML(el.map.page), 
+						new Element('span',{'class':'small'}).setHTML(" ("+el.map.score+")")
+					).inject(frag);
+				});
+				$('searchOutput').empty().adopt(frag);
+				Wiki.locatemenu( $('query'), $('searchboxMenu') );
+		});
+	} ,
+
+
+	/* navigate to url, after smart pagename handling */
+	navigate: function(url, promptText, clone, search){
+		var p = Wiki.PageName, s = this.query.value;
+		if(s == this.query.defaultValue) s = '';
+
+		if(s == ''){
+			s = prompt(promptText, (clone) ? p+'sbox.clone.suffix'.localize() : p);
+			if( !s || (s == '') ) return false;
+		}
+		//if(!search) s = s.replace(/[^A-Za-z0-9._\/]/g, ''); //valid pagename FIXME
+		if( clone && (s != p) )  s += '&clone=' + p;
+
+		if(s == '') return false; //dont exec the click
+		location.href = url.replace('__PAGEHERE__', s);
+	}
+}
+
+
+/**
+ ** 150 GraphBar Object: also used on the findpage
+ ** %%graphBars ... %%
+ ** convert numbers inside %%gBar ... %% tags to graphic horizontal bars
+ ** no img needed.
+ ** supported parameters: bar-color and bar-maxsize
+ ** e.g. %%graphBars-e0e0e0 ... %%  use color #e0e0e0, default size 120
+ ** e.g. %%graphBars-blue-red ... %%  blend colors from blue to red
+ ** e.g. %%graphBars-red-40 ... %%  use color red, maxsize 40 chars
+ ** e.g. %%graphBars-vertical ... %%  vertical bars
+ ** e.g. %%graphBars-progress ... %%  progress bars in 2 colors
+ ** e.g. %%graphBars-gauge ... %%  gauge bars in gradient colors
+ **/
+
+/* minimal variant of the Color class, inspired by mootools */
+var Color = new Class({
+
+	_HTMLColors: {
+		black  :"000000", green :"008000", silver :"c0c0c0", lime  :"00ff00",
+		gray   :"808080", olive :"808000", white  :"ffffff", yellow:"ffff00",
+		maroon :"800000", navy  :"000080", red    :"ff0000", blue  :"0000ff",
+		purple :"800080", teal  :"008080", fuchsia:"ff00ff", aqua  :"00ffff" 
+	},
+	
+	initialize: function(color, type){
+		type = type || (color.push ? 'rgb' : 'hex');
+		if(this._HTMLColors[color]) color = this._HTMLColors[color];
+		var rgb = (type=='rgb') ? color : color.toString().hexToRgb(true);
+		if(!rgb) return false;
+		rgb.hex = rgb.rgbToHex();
+		return $extend(rgb, Color.prototype);
+	},
+
+	mix: function(){
+		var colors = $A(arguments),
+			rgb = this.copy(),
+			alpha = (($type(colors[colors.length - 1]) == 'number') ? colors.pop() : 50)/100,
+			alphaI = 1-alpha;
+		
+		colors.each(function(color){
+			color = new Color(color);
+			for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] * alphaI) + (color[i] * alpha));
+		});
+		return new Color(rgb, 'rgb');
+	},
+
+	invert: function(){
+		return new Color(this.map(function(value){
+			return 255 - value;
+		}));
+	}
+
+});
+
+var GraphBar =
+{
+	onPageLoad : function(){
+		$$('*[class^=graphBars]').each( function(g){
+			var lbound = 20,	//max - lowerbound size of bar
+				ubound = 320,	//min - upperbound size of bar
+				vwidth = 20,	//vertical bar width
+				color1 = null,	// bar color
+				color2 = null,	// 2nd bar color used depending on bar-type
+				isGauge = false,	// gauge bar
+				isProgress = false,	// progress bar
+				isHorizontal = true,// horizontal or vertical orientation
+				parms = g.className.substr(9).split('-'),
+				barName = parms.shift(); //first param is optional barName
+			
+			parms.each(function(p){
+				p = p.toLowerCase();
+				if(p == "vertical") { isHorizontal = false; }
+				else if(p == "progress") { isProgress = true;  }
+				else if(p == "gauge") { isGauge = true; }
+				else if(p.indexOf("min") == 0) { lbound = p.substr(3).toInt(); }
+				else if(p.indexOf("max") == 0) { ubound = p.substr(3).toInt(); }
+				else if(p != "") {
+					p = new Color(p,'hex'); if(!p.hex) return;
+					if(!color1) color1 = p; 
+					else if(!color2) color2 = p;
+				}
+			});
+			if( !color2 && color1) color2 = (isGauge || isProgress) ? color1.invert() : color1;
+
+			if( lbound > ubound ) { var m = ubound; ubound=lbound; ubound=m; }
+			var size = ubound-lbound;
+
+			var bars = $ES('.gBar'+barName, g); //collect all gBar elements
+			if( (bars.length==0) && barName && (barName!="")){  // check table data
+				bars = this.getTableValues(g, barName);
+			}
+			if( !bars ) return;
+
+			var barData = this.parseBarData( bars, lbound, size ),
+				border = (isHorizontal ? 'borderLeft' : 'borderBottom');
+
+			bars.each(function(b,j){
+				var bar1 = $H().set(border+'Width',barData[j]), 
+					bar2 = $H(), // 2nd bar only valid ico 'progress' 
+					barEL = new Element('span',{'class':'graphBar'}),
+					pb = b.getParent(); // parent of gBar element
+
+				if(isHorizontal){
+					barEL.setHTML('x');
+					if(isProgress){	
+						bar2.extend(bar1.obj);
+						bar1.set(border+'Width',ubound-barData[j]).set('marginLeft','-1ex'); 
+					}					
+				} else { // isVertical
+					if(pb.getTag()=='td') { pb = new Element('div').injectWrapper(pb); }
+
+					pb.setStyles({'height':ubound+b.getStyle('lineHeight').toInt(), 'position':'relative'});
+					b.setStyle('position', 'relative'); //needed for inserted spans ;-)) hehe
+					if( !isProgress ) { b.setStyle('top', (ubound-barData[j])); }
+
+					bar1.extend({'position':'absolute', 'width':vwidth, 'bottom':'0'});
+					if(isProgress){ 
+						bar2.extend(bar1.obj).set(border+'Width', ubound); 
+					}
+				}
+				if(isProgress){
+					if(color1){ bar1.set('borderColor', color1.hex); }
+					if(color2){ 
+						bar2.set('borderColor', color2.hex); 
+					} else { 
+						bar1.set('borderColor', 'transparent');
+					}
+				} else if(color1){
+					var percent = isGauge ? (barData[j]-lbound)/size : j/(bars.length-1);
+					bar1.set('borderColor', color1.mix(color2, 100 * percent).hex);
+				}
+				
+				if(bar2.length > 0){ barEL.clone().setStyles(bar2.obj).injectBefore(b); };
+				if(bar1.length > 0){ barEL.setStyles(bar1.obj).injectBefore(b); };
+			},this);
+
+		},this);
+	},
+
+	// parse bar data types and scale according to lbound and size
+	parseBarData: function(nodes, lbound, size){
+		var barData=[], 
+			maxValue=Number.MIN_VALUE, 
+			minValue=Number.MAX_VALUE,
+			num=date=true;
+	
+		nodes.each(function(n,i){
+			var s = n.getText();
+			barData.push(s);
+			if(num) num = !isNaN(parseFloat( s.match(Number.REparsefloat) ) );
+			if(date) date = !isNaN(Date.parse(s));
+		});
+		barData = barData.map(function(b){
+			if(date)     { b = new Date(Date.parse(b) ).valueOf();  }
+			else if(num) { b = parseFloat( b.match(Number.REparsefloat) ); }
+			
+			maxValue = Math.max(maxValue, b);
+			minValue = Math.min(minValue, b);
+			return b;
+		});		
+
+		if(maxValue==minValue) maxValue=minValue+1; /* avoid div by 0 */
+		size = size/(maxValue-minValue);
+		return barData.map(function(b){
+			return ( (size*(b-minValue)) + lbound).toInt();
+		});
+	},
+
+	/* Fetch set of gBar values from a table
+	 * Check first-row to match field-name: return array with col values
+	 * Check first-column to match field-name: return array with row values
+	 * insert SPANs as place-holder of the missing gBars
+	 */
+	getTableValues: function(node, fieldName){
+		var table = $E('table', node); if(!table) return false;
+		var tlen = table.rows.length;
+
+		if( tlen > 1 ){ /* check for COLUMN based table */
+			var r = table.rows[0];
+			for( var h=0; h < r.cells.length; h++ ){
+				if( $getText( r.cells[h] ).trim() == fieldName ){
+					var result = [];
+					for( var i=1; i< tlen; i++)
+						result.push( new Element('span').injectWrapper(table.rows[i].cells[h]) );
+					return result;
+				}
+			}
+		}
+		for( var h=0; h < tlen; h++ ){  /* check for ROW based table */
+			var r = table.rows[h];
+			if( $getText( r.cells[0] ).trim() == fieldName ){
+				var result = [];
+				for( var i=1; i< r.cells.length; i++)
+					result.push( new Element('span').injectWrapper(r.cells[i]) );
+				return result;
+			}
+		}
+		return false;
+	}
+}
+
+/** 200 Collapsible list and boxes
+ ** See also David Lindquist <first name><at><last name><dot><net>
+ ** See: http://www.gazingus.org/html/DOM-Scripted_Lists_Revisited.html
+ **
+ ** Add support for collabsable boxes, Nov 05, D.Frederickx
+ ** Refactored on mootools, including effects, May 07, D.Frederickx
+ **/
+var Collapsible =
+{
+	pims : [], // all me cookies
+
+	onPageLoad: function(){
+		this.bullet = new Element('div',{'class':'collapseBullet'}).setHTML('&bull;');
+		this.initialise( "favorites",   "JSPWikiCollapseFavorites" );
+		this.initialise( "pagecontent", "JSPWikiCollapse" + Wiki.PageName );
+		this.initialise( "previewcontent", "JSPWikiCollapse" + Wiki.PageName );
+		this.initialise( "info" );
+	},
+
+	initialise: function( page, cookie){
+		page = $(page); if(!page) return;
+
+		this.pims.push({
+			'name':cookie,
+			'value':'',
+			'initial': (cookie ? Cookie.get(cookie) : "") 
+		});
+		$ES('.collapse', page).each(function(el){ 
+			if( $E('.collapseBullet',el) ) return; /* no nesting */
+			this.collapseNode(el); 
+		}, this);
+		$ES('.collapsebox,.collapsebox-closed', page).each(function(el){ 
+			this.collapseBox(el); 
+		}, this);	
+	},
+
+	collapseBox: function(el){
+		var title = el.getFirst(); if( !title ) return;
+		var body = new Element('div', {'class':'collapsebody'}), 
+			bullet  = this.bullet.clone(),
+			isclosed = el.hasClass('collapsebox-closed');
+
+		while(title.nextSibling) body.appendChild(title.nextSibling); // wrap other siblings
+		el.appendChild(body);
+		
+		if(isclosed) el.removeClass('collapsebox-closed').addClass('collapsebox');
+		bullet.injectTop( title.addClass('collapsetitle') );
+		this.newBullet(bullet, body, !isclosed, title );
+	},
+
+	// Modifies the list such that sublists can be hidden/shown by clicking the listitem bullet
+	// The listitem bullet is a node inserted into the DOM tree as the first child of the
+	// listitem containing the sublist.
+	collapseNode: function(node){
+		$ES('li',node).each(function(li){
+			var ulol = $E('ul',li) || $E('ol',li);
+			
+			//dont insert bullet when LI is 'empty': no text or no non-ul/ol tags			
+			var emptyLI = true;
+			for( var n = li.firstChild; n ; n = n.nextSibling ) {
+				if((n.nodeType == 3 ) && ( n.nodeValue.trim() == "" ) ) continue; //keep searching
+				if((n.nodeName == "UL") || (n.nodeName == "OL")) break; //seems like an empty li
+				emptyLI = false;
+				break;
+			}
+			if( emptyLI ) return;
+			
+			new Element('div',{'class':'collapsebody'}).injectWrapper(li);
+			var bullet = this.bullet.clone().injectTop(li);
+			if(ulol) this.newBullet(bullet, ulol, (ulol.getTag()=='ul'));
+		},this);
+	},
+
+	newBullet: function(bullet, body, isopen, clicktarget){
+		var ck = this.pims.getLast(); //read cookie
+		isopen = this.parseCookie(isopen);
+		if(!clicktarget) clicktarget = bullet;
+
+		var bodyfx = body.setStyle('overflow','hidden')
+			.effect('height', { 
+				wait:false,
+				onStart:this.renderBullet.bind(bullet),
+				onComplete:function(){ if(bullet.hasClass('collapseOpen')) body.setStyle('height','auto'); } 
+			});
+
+		bullet.className = (isopen ? 'collapseClose' : 'collapseOpen'); //ready for rendering
+		clicktarget.addEvent('click', this.clickBullet.bind(bullet, [ck, ck.value.length-1, bodyfx]))
+			.addEvent('mouseenter', function(){ clicktarget.addClass('collapseHover')} )
+			.addEvent('mouseleave', function(){ clicktarget.removeClass('collapseHover')} );
+			  
+		bodyfx.fireEvent('onStart');
+		if(!isopen) bodyfx.set(0); //.set( isopen ? body.scrollHeight : 0 );	
+	},
+
+	renderBullet: function(){
+		if(this.hasClass('collapseClose')){
+			this.setProperties({'title':'collapse'.localize(), 'class':'collapseOpen'}).setHTML('-'); /* &raquo; */
+		} else {
+			this.setProperties({'title':'expand'.localize(), 'class':'collapseClose'}).setHTML('+'); /* &laquo; */
+		}
+	},
+
+	clickBullet: function( ck, bulletidx, bodyfx){
+		var collapse = this.hasClass('collapseOpen'),
+			bodyHeight = bodyfx.element.scrollHeight; 
+
+		if(collapse) bodyfx.start(bodyHeight, 0); else bodyfx.start(bodyHeight);
+		
+		ck.value = ck.value.substring(0,bulletidx) + (collapse ? 'c' : 'o') + ck.value.substring(bulletidx+1) ;
+		if(ck.name) Cookie.set(ck.name, ck.value, {path:Wiki.BasePath, duration:20});
+	},
+
+	// parse initial cookie versus actual document 
+	// returns true if collapse status is open
+	parseCookie: function( isopen ){
+		var ck = this.pims.getLast(),
+			cursor = ck.value.length,
+			token = (isopen ? 'o' : 'c');
+
+		if(ck.initial && (ck.initial.length > cursor)){
+			var cookieToken = ck.initial.charAt( cursor );
+
+			if(  ( isopen && (cookieToken == 'c') )
+			  || ( !isopen && (cookieToken == 'o') ) ) token = cookieToken ;
+
+			if(token != cookieToken) ck.initial = null; //mismatch with initial cookie
+		}
+		ck.value += token; //append and save currentcookie
+
+		return(token == 'o');
+	}
+}
+
+
+/** 220 RoundedCorners --experimental
+ ** based on Nifty corners by Allesandro Fulciniti
+ ** www.pro.html.it
+ ** Refactored for JSPWiki
+ **
+ ** JSPWiki syntax:
+ **
+ **  %%roundedCorners-<corners>-<color>-<borderColor>
+ **  %%
+ **
+ **  roundedCorners-yyyy-ffc5ff-c0c0c0
+ **
+ **  corners: "yyyy" where first y: top-left,    2nd y: top-right,
+ **                           3rd y: bottom-left; 4th y: bottom-right
+ **     value can be: "y": Normal rounded corner (lowercase y)
+ **                    "s": Small rounded corner (lowercase s)
+ **                    "n": Normal square corner
+ **
+ **/
+var RoundedCorners =
+{
+	/** Definition of CORNER dimensions
+	 ** Normal    Normal+Border  Small  Small+Border
+	 ** .....+++  .....BBB       ..+++  ..BBB
+	 ** ...+++++  ...BB+++       .++++  .B+++
+	 ** ..++++++  ..B+++++       +++++  B++++
+	 ** .+++++++  .B++++++
+	 ** .+++++++  .B++++++
+	 ** ++++++++  B+++++++
+	 **
+	 ** legend: . background, B border, + forground color
+	 **/
+	NormalTop :
+		 [ { margin: "5px", height: "1px", borderSide: "0", borderTop: "1px" }
+		 , { margin: "3px", height: "1px", borderSide: "2px" }
+		 , { margin: "2px", height: "1px", borderSide: "1px" }
+		 , { margin: "1px", height: "2px", borderSide: "1px" }
+		 ] ,
+	SmallTop :
+		 [ { margin: "2px", height: "1px", borderSide: "0", borderTop: "1px" }
+		 , { margin: "1px", height: "1px", borderSide: "1px" }
+		 ] ,
+	//NormalBottom: see onPageLoad()
+	//SmallBottom: see onPageLoad()
+
+	/**
+	 ** Usage:
+	 ** RoundedCorners.register( "#header", ['yyyy', '00f000', '32cd32'] );
+	 **/
+	registry: {},
+	register: function( selector, parameters )
+	{
+		this.registry[selector] = parameters;
+		return this;
+	},
+
+	onPageLoad: function()
+	{
+		/* make reverse copies for bottom definitions */
+		this.NormalBottom = this.NormalTop.slice(0).reverse();
+		this.SmallBottom  = this.SmallTop.slice(0).reverse();
+
+		for( selector in this.registry )  // CHECK NEEDED
+		{
+			var n = $$(selector); 
+			var parms = this.registry[selector];
+			this.exec( n, parms[0], parms[1], parms[2], parms[3] );
+		}
+
+		$$('#pagecontent *[class^=roundedCorners]').each(function(el){ 
+			var parms = el.className.split('-');
+			if( parms.length < 2 ) return;
+			this.exec( [el], parms[1], parms[2], parms[3], parms[4] );
+		},this);
+	},
+
+	exec: function( nodes, corners, color, borderColor, background )
+	{
+		corners = ( corners ? corners+"nnnn": "yyyy" );
+		color   = new Color(color,'hex') || 'transparent';
+		if(borderColor) borderColor = new Color(borderColor);
+		if(background)  background  = new Color(background);
+
+		var c = corners.split('');
+		/* [0]=top-left; [1]=top-right; [2]=bottom-left; [3]=bottom-right; */
+
+		var nodeTop = null;
+		var nodeBottom = null;
+
+		if( c[0]+c[1] != "nn" )  //add top rounded corners
+		{
+			nodeTop = document.createElement("b") ;
+			nodeTop.className = "roundedCorners" ;
+
+			if( (c[0] == "y") || (c[1] == "y") )
+			{
+				this.addCorner( nodeTop, this.NormalTop, c[0], c[1], color, borderColor );
+			}
+			else if( (c[0] == "s") || (c[1] == "s") )
+			{
+				this.addCorner( nodeTop, this.SmallTop, c[0], c[1], color, borderColor );
+			}
+		}
+
+		if( c[2]+c[3] != "nn" ) //add bottom rounded corners
+		{
+			nodeBottom = document.createElement("b");
+			nodeBottom.className = "roundedCorners";
+
+			if( (c[2] == "y") || (c[3] == "y") )
+			{
+				this.addCorner( nodeBottom, this.NormalBottom, c[2], c[3], color, borderColor );
+			}
+			else if( (c[2] == "s") || (c[3] == "s") )
+			{
+				this.addCorner( nodeBottom, this.SmallBottom, c[2], c[3], color, borderColor );
+			}
+		}
+
+		if( (!nodeTop) && (!borderColor) && (!nodeBottom) ) return;
+
+		for( var i=0; i<nodes.length; i++)
+		{
+			if( !nodes[i] ) continue;
+			this.addBody( nodes[i], color, borderColor );
+			if( nodeTop     )  nodes[i].insertBefore( nodeTop.cloneNode(true), nodes[i].firstChild );
+			if( nodeBottom  )  nodes[i].appendChild( nodeBottom.cloneNode(true) );
+		}
+	},
+
+	addCorner: function( node, arr, left, right, color, borderColor )
+	{
+		for( var i=0; i< arr.length; i++ )
+		{
+			var n =  document.createElement("div");
+			n.style.height = arr[i].height;
+			n.style.overflow = "hidden";
+			n.style.borderWidth = "0";
+			n.style.backgroundColor = color.hex;
+
+			if( borderColor )
+			{
+				n.style.borderColor = borderColor.hex;
+				n.style.borderStyle = "solid";
+				if(arr[i].borderTop)
+				{
+					n.style.borderTopWidth = arr[i].borderTop;
+					n.style.height = "0";
+				}
+			}
+
+			if( left != 'n' ) n.style.marginLeft = arr[i].margin;
+			if( right != 'n' ) n.style.marginRight = arr[i].margin;
+			if( borderColor )
+			{
+				n.style.borderLeftWidth  = ( left  == 'n' ) ? "1px": arr[i].borderSide;
+				n.style.borderRightWidth = ( right == 'n' ) ? "1px": arr[i].borderSide;
+			}
+			node.appendChild( n );
+		}
+	},
+
+	// move all children of the node inside a DIV and set color and bordercolor
+	addBody: function( node, color, borderColor)
+	{
+		if( node.passed ) return;
+
+		var container = new Element('div').injectWrapper(node);
+
+		container.style.padding = "0 4px";
+		container.style.backgroundColor = color.hex;
+		if( borderColor )
+		{
+			container.style.borderLeft  = "1px solid " + borderColor.hex;
+			container.style.borderRight = "1px solid " + borderColor.hex;
+		}
+
+		node.passed=true;
+	}
+}
+
+
+/** 230 Sortable -- Sort tables **/
+//TODO cache table ok, cache datatype for each column
+var Sortable =
+{
+	onPageLoad: function(){
+		this.DefaultTitle = "sort.click".localize();
+		this.AscendingTitle = "sort.ascending".localize();
+		this.DescendingTitle = "sort.descending".localize();
+		
+		$$('.sortable table').each(function(table){
+			if( table.rows.length < 2 ) return;
+
+			$A(table.rows[0].cells).each(function(th){
+				th=$(th);
+				if( th.getTag() != 'th' ) return;
+				th.addEvent('click', function(){ Sortable.sort(th); })
+					.addClass('sort')
+					.title=Sortable.DefaultTitle;
+			});
+		},this);
+	},
+
+	sort: function(th){
+		var table = getAncestorByTagName(th, "table" ),
+			filter = (table.filterStack),
+			rows = (table.sortCache || []),
+			colidx = 0, //target column to sort
+			body = $T(table); 
+		th = $(th);
+
+		//todo add spinner while sorting
+		//validate header row
+		$A(body.rows[0].cells).each(function(thi, i){
+			if(thi.getTag() != 'th') return;
+			if(th == thi) { colidx=i; return; }
+			thi.removeClass('sortAscending').removeClass('sortDescending')
+				.addClass('sort').title = Sortable.DefaultTitle;
+		});
+
+		if(rows.length == 0){  //if data not yet cached
+			$A(body.rows).each(function(r,i){
+				if((i==0) || ((i==1) && (filter))) return;
+				rows.push( r );
+			});
+		};		
+		var datatype = Sortable.guessDataType(rows,colidx);
+
+		//do the actual sorting
+		if(th.hasClass('sort')){ 
+			rows.sort( Sortable.createCompare(colidx, datatype) )
+		}
+		else rows.reverse(); 
+		
+		var fl=th.hasClass('sortDescending');
+		th.removeClass('sort').removeClass('sortAscending').removeClass('sortDescending');
+		th.addClass(fl ? 'sortAscending': 'sortDescending')
+			.title= fl ? Sortable.DescendingTitle: Sortable.AscendingTitle;
+		
+		var frag = document.createDocumentFragment();
+		rows.each( function(r,i){ frag.appendChild(r); });
+		body.appendChild(frag);
+		table.sortCache = rows;
+		if(table.zebra) table.zebra();
+	},
+
+	guessDataType: function(rows, colidx){
+		var num=date=ip4=euro=true;
+		rows.each(function(r,i){
+			var v = $getText(r.cells[colidx]).clean().toLowerCase();
+			if(num)  num  = !isNaN(parseFloat(v));
+			if(date) date = !isNaN(Date.parse(v));
+			if(ip4)  ip4  = v.test("(?:\\d{1,3}\\.){3}\\d{1,3}");
+			if(euro) euro = v.test("^[£$€][0-9.,]+");
+		});
+		return (euro) ? 'euro': (ip4) ? 'ip4': (date) ? 'date': (num) ? 'num': 'string';
+	},
+
+	convert: function(val, datatype){
+		switch(datatype){
+			case "num" : return parseFloat( val.match( Number.REparsefloat ) );
+			case "euro": return parseFloat( val.replace(/[^0-9.,]/g,'') );
+			case "date": return new Date( Date.parse( val ) );
+			case "ip4" : 
+				var octet = val.split( "." );
+				return parseInt(octet[0]) * 1000000000 + parseInt(octet[1]) * 1000000 + parseInt(octet[2]) * 1000 + parseInt(octet[3]);
+			default    : return val.toString().toLowerCase();
+		}
+	},
+
+	createCompare: function(i, datatype) {
+		return function(row1, row2) {
+			var val1 = Sortable.convert( $getText(row1.cells[i]), datatype );
+			var val2 = Sortable.convert( $getText(row2.cells[i]), datatype );
+
+			if(val1<val2){ return -1 } else if(val1>val2){ return 1 } else return 0;
+		}
+	}
+}
+
+/** 240 table-filters 
+ ** inspired by http://www.codeproject.com/jscript/filter.asp
+ **/
+var TableFilter =
+{
+	onPageLoad: function(){
+		this.All = "filter.all".localize();
+		this.FilterRow = 1; //row number of filter dropdowns
+		
+		$$('.table-filter table').each( function(table){
+			if( table.rows.length < 2 ) return;
+
+			/*
+			$A(table.rows[0].cells).each(function(e,i){
+				var s = new Element('select',{ 
+					'events': { 
+						'click':function(event){ event.stop(); }.bindWithEvent(), 
+						'change':TableFilter.filter 
+					} 
+				});
+				s.fcol = i; //store index
+				e.adopt(s);	        
+			},this);
+			*/
+			
+			var r = $(table.insertRow(TableFilter.FilterRow)).addClass('filterrow');
+			for(var j=0; j < table.rows[0].cells.length; j++ ){
+				var s = new Element('select',{ 
+					'events': { 
+						'change':TableFilter.filter 
+					} 
+				});
+				s.fcol = j; //store index
+				
+				new Element('th').adopt(s).inject(r);
+			}
+			table.filterStack = [];
+			TableFilter.buildEmptyFilters(table);
+		});
+	},
+
+	buildEmptyFilters: function(table){
+		for(var i=0; i < table.rows[0].cells.length; i++){
+			var ff = table.filterStack.some(function(f){ return f.fcol==i });
+			if(!ff) TableFilter.buildFilter(table, i);
+		}
+		if(table.zebra) table.zebra();			
+	},
+
+	// this function initialises a column dropdown filter
+	buildFilter: function(table, col, selectedValue){
+		// Get a reference to the dropdownbox.
+		var select = table.rows[TableFilter.FilterRow].cells[col].firstChild;
+		//var select = $(table.rows[0].cells[col]).getLast();
+		if(!select) return; //empty dropdown
+		select.options.length = 0;
+
+		var rows=[];
+		$A(table.rows).each(function(r,i){
+			if((i==0) || (i==TableFilter.FilterRow)) return;
+			if(r.style.display == 'none') return;
+			rows.push( r );
+		});
+		rows.sort(Sortable.createCompare(col, Sortable.guessDataType(rows,col)));
+
+		//add only unique strings to the dropdownbox
+		select.options[0]= new Option(this.All, this.All);
+		var value;
+		rows.each(function(r,i){
+			var v = $getText(r.cells[col]).clean().toLowerCase();
+			if(v == value) return;
+			value = v;
+			if(v.length > 32) v = v.substr(0,32)+ "...";
+			select.options[select.options.length] = new Option(v, value);
+		});
+		(select.options.length <= 2) ? select.hide() : select.show();
+		if(selectedValue != undefined) {
+			select.value = selectedValue;
+		} else {
+			select.options[0].selected = true;
+		}
+	},
+
+	filter: function(){ //onchange handler of filter dropdowns
+		var col   = this.fcol,
+			value = this.value,
+			table = getAncestorByTagName(this, 'table');
+		if( !table || table.style.display == 'none') return;
+
+		// First check if the column is allready in the filter.
+		if(table.filterStack.every(function(f,i){
+			if(f.fcol != col) return true;
+			if(value == TableFilter.All) table.filterStack.splice(i, 1);
+			else f.fValue = value;
+			return false;
+		}) ) table.filterStack.push( {fValue:value, fcol:col} );
+
+		$A(table.rows).each(function(r,i){ //show all
+			r.style.display='';
+		});
+
+		table.filterStack.each(function(f){ //now filter the right rows
+			var v = f.fValue, c = f.fcol;
+			TableFilter.buildFilter(table, c, v);
+
+			var j=0;
+			$A(table.rows).each(function(r,i){
+				if((i==0) || (i==TableFilter.FilterRow)) return;
+				if(v != $getText(r.cells[c]).clean().toLowerCase()) r.style.display = 'none';
+			});
+		});
+		TableFilter.buildEmptyFilters(table); //fill remaining dropdowns
+	}
+}
+
+
+/** 250 Categories: turn wikipage link into AJAXed popup **/
+var Categories =
+{
+	onPageLoad: function (){
+		this.jsp = Wiki.TemplateDir + '/AJAXCategories.jsp';
+
+		$$('.category a.wikipage').each(function(link){
+			var page = Wiki.getPageName(link.href); if(!page) return;
+			var wrap = new Element('span').injectBefore(link).adopt(link),
+				popup = new Element('div',{'class':'categoryPopup'}).inject(wrap),
+				popfx = popup.effect('opacity',{wait:false}).set(0);
+
+			link.addClass('categoryLink')
+				.setProperties({ href:'#', title: "category.title".localize(page) })
+				.addEvent('click', function(e){
+				new Event(e).stop();  //dont jump to top of page ;-)
+				new Ajax( Categories.jsp, { 
+					postBody: '&page=' + page,
+					update: popup,
+					onComplete: function(){
+						link.setProperty('title', '').removeEvent('click');
+						wrap.addEvent('mouseover', function(e){ popfx.start(0.9); })
+							.addEvent('mouseout', function(e){ popfx.start(0); });
+						popup.setStyle('left', link.getPosition().x);
+						popfx.start(0.9); 
+					}
+				}).request();
+			});
+		});
+	} 
+}
+
+/**
+ ** 260 Wiki Tips: 
+ **/
+var WikiTips =
+{
+	onPageLoad: function() {    
+		var tips = [];
+		$$('*[class^=tip]').each( function(t){
+			var parms = t.className.split('-');
+			if( parms.length<=0 || parms[0] != 'tip' ) return;
+			t.className = "tip";
+
+			var body = new Element('span').injectWrapper(t).hide(),
+				caption = (parms[1]) ? parms[1].deCamelize(): "tip.default.title".localize();
+
+			tips.push( 
+				new Element('span',{
+					'class': 'tip-anchor',
+					'title': caption + '::' + body.innerHTML
+				}).setHTML(caption).inject(t)
+			);
+		});
+		if( tips.length>0 ) new Tips( tips , {'className':'tip'} );
+	}
+}
+
+
+/**
+ ** 270 Wiki Columns
+ ** Dirk Frederickx, Mar 07
+ **/
+var WikiColumns =
+{
+	onPageLoad: function() {    
+		var tips = [];
+		$$('*[class^=columns]').each( function(t){
+			var parms = t.className.split('-');
+			t.className='columns';
+			WikiColumns.buildColumns(t, parms[1] || 'auto');
+		});
+	},
+
+	buildColumns: function( el, width){
+		var breaks = $ES('hr',el);
+		if(!breaks || breaks.length==0) return;
+
+		var colCount = breaks.length+1;
+		width = (width=='auto') ? 98/colCount+'%' : width/colCount+'px';
+
+		var colDef = new Element('div',{'class':'col','styles':{'width':width}}),
+			col = colDef.clone().injectBefore(el.getFirst()),
+			n;
+		while(n = col.nextSibling){
+			if(n.tagName && n.tagName.toLowerCase() == 'hr'){
+				col = colDef.clone();
+				$(n).replaceWith(col);
+				continue;
+			}
+			col.appendChild(n);
+		}
+		new Element('div',{'styles':{'clear':'both'}}).inject(el);
+	}
+}
+
+/** 280 ZebraTable
+ ** Color odd/even rows of table differently
+ ** 1) odd rows get css class odd (ref. jspwiki.css )
+ **   %%zebra-table ... %%
+ **
+ ** 2) odd rows get css style='background=<color>'
+ ** %%zebra-<odd-color> ... %%
+ **
+ ** 3) odd rows get odd-color, even rows get even-color
+ ** %%zebra-<odd-color>-<even-color> ... %%
+ **
+ ** colors are specified in HEX (without #) format or html color names (red, lime, ...)
+ **/
+var ZebraTable = {
+
+	onPageLoad: function(){
+		$$('*[class^=zebra]').each(function(z){
+			var parms = z.className.split('-'), 
+				isDefault = parms[1].test('table'),
+				c1 = '', 
+				c2 = '';
+			if(parms[1]) c1= new Color(parms[1],'hex');
+			if(parms[2]) c2= new Color(parms[2],'hex');
+			$ES('table',z).each(function(t){
+				t.zebra = this.zebrafy.pass([isDefault, c1,c2],t);
+				t.zebra();
+			},this);
+		},this);
+	},
+	zebrafy: function(isDefault, c1,c2){
+		var j=0;
+		$A($T(this).rows).each(function(r,i){
+			if(i==0 || (r.style.display=='none')) return;
+			if(isDefault) (j++ % 2 == 0) ? $(r).addClass('odd') : $(r).removeClass('odd');
+			else $(r).setStyle('background-color', (j++ % 2 == 0) ? c1 : c2 );
+		});
+	}
+}
+
+
+/** Highlight Word
+ ** Inspired by http://www.kryogenix.org/code/browser/searchhi/
+ ** Modified 21006 to fix query string parsing and add case insensitivity
+ ** Modified 20030227 by sgala@hisitech.com to skip words
+ **                   with "-" and cut %2B (+) preceding pages
+ ** Refactored for JSPWiki -- now based on regexp, by D.Frederickx. Nov 2005
+ **/
+var HighlightWord =
+{
+	ReQuery: new RegExp( "(?:\\?|&)(?:q|query)=([^&]*)", "g" ),
+
+	onPageLoad: function (){
+		var q = Wiki.prefs.get('PrevQuery'); Wiki.prefs.set('PrevQuery', '');
+		if( !q && this.ReQuery.test(document.referrer)) q = RegExp.$1; 
+		if( !q ) return;
+
+		var words = decodeURIComponent(q);
+		words = words.replace( /\+/g, " " );
+		words = words.replace( /\s+-\S+/g, "" );
+		words = words.replace( /([\(\[\{\\\^\$\|\)\?\*\.\+])/g, "\\$1" ); //escape metachars
+		words = words.trim().split(/\s+/).join("|");
+		this.reMatch = new RegExp( "(" + words + ")" , "gi");
+
+		this.walkDomTree( $("pagecontent") );
+	},
+
+	// recursive tree walk matching all text nodes
+	walkDomTree: function( node )
+	{
+		if( !node ) return; /* bugfix */
+		var nn = null;
+		for( var n = node.firstChild; n ; n = nn ) {
+			nn = n. nextSibling; /* prefetch nextSibling cause the tree will be modified */
+			this.walkDomTree( n );
+		}
+		// continue on text-nodes, not yet highlighted, with a word match
+		if( node.nodeType != 3 ) return;
+		if( node.parentNode.className == "searchword" ) return;
+		var s = node.innerText || node.textContent || '';
+		if( !this.reMatch.test( s ) ) return;
+		var tmp = new Element('span').setHTML(s.replace(this.reMatch,"<span class='searchword'>$1</span>"));
+
+		var f = document.createDocumentFragment();
+		while( tmp.firstChild ) f.appendChild( tmp.firstChild );
+
+		node.parentNode.replaceChild( f, node );
+	}
+}
+
+
+/* 300 Javascript Code Prettifier
+ * based on http://google-code-prettify.googlecode.com/svn/trunk/README.html
+ */
+var WikiPrettify = {
+	onPageLoad: function(){
+		var els = $$('.prettify pre, .prettify code'); if(!els) return;
+
+		//TODO: load assets .css and .js
+		els.addClass('prettyprint');
+		prettyPrint();
+	}
+}
+
+
+window.addEvent('load', function(){
+
+	Wiki.onPageLoad();
+	WikiReflection.onPageLoad(); //before accordion cause impacts height!
+	WikiAccordion.onPageLoad();
+
+	TabbedSection.onPageLoad(); //after coordion or safari
+	QuickLinks.onPageLoad();
+	
+	//console.profile();
+	Collapsible.onPageLoad();
+	//console.profileEnd();
+
+	SearchBox.onPageLoad();
+	Sortable.onPageLoad();
+	TableFilter.onPageLoad();
+	RoundedCorners.onPageLoad();
+	ZebraTable.onPageLoad();
+	HighlightWord.onPageLoad();
+	GraphBar.onPageLoad();
+	Categories.onPageLoad();
+
+	WikiSlimbox.onPageLoad();
+	WikiTips.onPageLoad();
+	WikiColumns.onPageLoad();
+	WikiPrettify.onPageLoad();
+});
\ No newline at end of file

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-edit.js
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-edit.js?rev=627259&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-edit.js (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-edit.js Tue Feb 12 22:11:16 2008
@@ -0,0 +1,496 @@
+/**
+ ** jspwiki edit support routines
+ ** Based on brushed template
+ ** Needs jspwiki-common.js and mootools.js
+ **
+ ** EditTools object (main object)
+ ** - find&replace functionality : with regexp support
+ **
+ ** - included popup-pagelinks routine from Janne // 
+ **
+ ** TextArea object
+ **     Supports selections inside textarea, in ie and other browsers
+ **/
+
+var EditTools = 
+{
+	onPageLoad: function(){
+
+		this.textarea = $('editorarea'); 
+		if(!this.textarea || !this.textarea.visible) return;
+		
+		window.onbeforeunload = (function(){
+			if(this.textarea.value != this.textarea.defaultValue) { 
+				return "edit.areyousure".localize();
+			};
+		}).bind(this);
+
+		/* make textarea more intelligent */
+		this.wikisnippets = this.getWikiSnippets();
+		this.wikismartpairs = this.getWikiSmartPairs();
+		this.onPageLoadPostEditor();
+
+		/* activate editassist toolbar */
+		var toolbar = $('toolbar');
+		var fxToolbar = new Fx.Slide(toolbar,{
+			onStart:function(){
+				$('editassist').toggleClass('closed');
+			}
+		});
+		var e = $('editassist').addEvent('click', function(e){
+			e = new Event(e);
+			fxToolbar.toggle();
+			e.stop();
+		}).getParent().show();
+
+
+		//FIXME: stop-event not yet working properly on eg UNDO
+		$('tbREDO').addEvent('click', function(e) { EditTools.redoTextarea(); new Event(e).stop(); });
+		$('tbUNDO').addEvent('click', function(e) { new Event(e).stop(); EditTools.undoTextarea();  });
+		$('replace').addEvent('click', function(e) { EditTools.doReplace(); new Event(e).stop(); })
+			.getParent().getParent().show();
+
+		toolbar.getElements('a.tool').each(function(el){
+			el.addEvent('click', this.insertTextArea.pass(el,this));
+		},this);
+
+		/* add textarea resize drag bar */
+		var hh=Wiki.prefs.get('EditorSize');
+		if(hh) this.textarea.setStyle('height',hh);
+		var h = new Element('div',{'class':'textarea-resizer', 'title':'edit.resize'.localize()})
+			.injectAfter(this.textarea);	
+		this.textarea.makeResizable({
+			handle:h, 
+			modifiers: {x:false, y:'height'}, 
+			onComplete: function(){	Wiki.prefs.set('EditorSize',this.value.now.y); }
+		});			
+		
+	},
+
+	onPageLoadPostEditor: function(){
+		if(window.ie) return;
+		this.posteditor = new postEditor.create(this.textarea,'changenote');
+		
+		/* patch posteditor DF Jul 07 */
+		/* righ-arrow nok on FF, nop on Safari */
+		this.posteditor.onKeyRight = Class.empty; 				
+		/* make posteditor changes undoable */
+		this.posteditor.value = function(value) {
+			EditTools.storeTextarea();
+			this.element.value = value.join("");
+		};
+
+		['smartpairs', 'tabcompletion'].each( function(el){
+			$(el).setProperty('checked', Wiki.prefs.get(el) || false)
+				 .addEvent('click',function(e) {
+					Wiki.prefs.set(el,this.checked);
+					EditTools.initPostEditor();
+				 });
+		},this);
+		$('smartpairs').getParent().show();
+				
+		this.initPostEditor();	
+	},	
+	initPostEditor: function(){
+		if(! this.posteditor) return;
+		this.posteditor.changeSmartTypingPairs( $('smartpairs').checked ? this.wikismartpairs : {} );
+		this.posteditor.changeSnippets( $('tabcompletion').checked ? this.wikisnippets : {} );	
+	},
+
+	getWikiSnippets: function(){
+		return {
+	"toc" : {
+		snippet:["","[{TableOfContents }]", "\n"],
+		tab:['[{TableOfContents }]', '']
+	},
+	"link" : {
+		snippet:["[","link text|pagename", "]"],
+		tab:['link text','pagename','']
+	},
+	"code" : {
+		snippet:["%%prettify \n{{{\n","some code block", "\n}}}\n/%\n"],
+		tab:['some code block','']
+	},
+	"pre" : {
+		snippet:["{{{\n","some preformatted block", "\n}}}\n"],
+		tab:['some preformatted block','']
+	},
+	"br" : {
+		snippet:['\\\\\n','',''],
+		tab:['']
+	},
+	"bold" : {
+		snippet:["__","some bold text", "__"],
+		tab:['some bold text','']
+	},
+	"italic" : {
+		snippet:["''","some italic text", "''"],
+		tab:['some italic text','']
+	},
+	"h1" : {
+		snippet:["!!! ","Heading 1 title", "\n"],
+		tab:["Heading 1 title", ""]
+	},
+	"h2" : {
+		snippet:["!! ","Heading 2 title", "\n"],
+		tab:["Heading 2 title", ""]
+	},
+	"h3" : {
+		snippet:["! ","Heading 3 title", "\n"],
+		tab:["Heading 3 title", ""]
+	},
+	"dl" : {
+		snippet:["\n",";term:definition text", "\n"],
+		tab:["term","definition text", ""]
+	},
+	"mono" : {
+		snippet:["{{","some monospaced text", "}}"],
+		tab:["some monospaced text", ""]
+	},
+	"hr" : {
+		snippet:['----\n','',''],
+		tab:['']
+	},
+	"sub" : {
+		snippet:["%%sub ","subscript text", "/%"],
+		tab:['subscript text','']
+	},
+	"sup" : {
+		snippet:["%%sup ","superscript text", "/%"],
+		tab:['superscript text','']
+	},
+	"strike" : {
+		snippet:["%%strike ","strikethrough text", "/%"],
+		tab:['strikethrough text','']
+	},
+	"tab" : {
+		snippet:["%%tabbedSection \n","%%tab-tabTitle1\ntab content 1\n/%\n%%tab-tabTitle2\ntab content 2", "\n/%\n/%\n"],
+		tab:['tabTitle1','tab content 1','tabTitle2','tab content 2','']
+	},
+	"table" : {
+		snippet:["\n","||heading 1||heading 2\n| cell 1   | cell 2", "\n"],
+		tab:['heading 1','heading 2','cell 1','cell 2','']
+	},
+	"img" : {
+		snippet:["","[{Image src='img.jpg' width='..' height='..' align='left|center|right' style='..' class='..' }]", "\n"],
+		tab:['img.jpg', '']
+	},
+	"quote" : {
+		snippet:["%%quote \n","quoted text", "\n/%\n"],
+		tab:['quoted text','']
+	},
+	"%%" : {
+		snippet:["%%","wikistyle\nsome text", "\n/%"],
+		tab:['wikistyle','some text','']
+	},
+	//dynamic snippets
+	"sign" : {
+		snippet:["\\\\\n--",Wiki.UserName+", "+"25 Sep 07","\n"],
+		tab:[Wiki.UserName,'25 Sep 07','']
+	},
+	/* TODO: how to insert the proper current date/timestamp, inline with the preferred time format */
+	"date" : {
+		//return new object snippet
+		command: function(k) {
+			var dayNames = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],
+			monthNames = ["January","February","March","April","May","June","July","August","September","October","November","December"],
+			dt = new Date(),
+			y  = dt.getYear();
+			if (y < 1000) y +=1900;
+			var date = dayNames[dt.getDay()] + ", " + monthNames[dt.getMonth()] + " " + dt.getDate() + ", " + y;
+			return {
+				//key:"date", optional
+				snippet:['',date,' '],
+				tab:[date,'']
+			};
+		}
+	}
+} /* return */
+
+	},
+	getWikiSmartPairs: function(){
+		return {
+		'"' : '"',
+		'(' : ')',
+		'{' : '}',
+		'[' : ']',
+		'<' : '>',
+		"'" : { scope:{ "{{{":"}}}" }, pair:"'" }
+		}
+	},
+	insertTextArea: function(el) {
+		var snippy = this.wikisnippets[el.getText()]; if(!snippy) return
+
+		var s = TextArea.getSelection(this.textarea),
+			t = snippy.snippet.join('');
+		EditTools.storeTextarea();
+
+		if((el.rel=='break') && (!TextArea.isSelectionAtStartOfLine(this.textarea))) { 
+			t = "\n" + t;
+		}
+		if(s) t = t.replace( snippy.tab[0], s)
+		TextArea.replaceSelection(this.textarea, t);
+		return false; /*don't propagate*/
+	} ,
+
+	/* TOOLBAR: find&replace */
+	doReplace: function( ){
+		var findText	= $('tbFIND').value, 
+			isRegExp	= $('tbREGEXP').checked,
+			reGlobal	= $('tbGLOBAL').checked ? 'g' : '',
+			replaceText	= $('tbREPLACE').value,
+			reMatchCase	= $('tbMatchCASE').checked ? '' : 'i';
+
+		if( findText == "") return;
+
+		var sel = TextArea.getSelection(this.textarea);
+		var data = ( !sel || (sel=="") ) ? this.textarea.value : sel;
+	
+		if(!isRegExp){ /* escape all special re characters */
+			var re = new RegExp( "([\.\*\\\?\+\[\^\$])", "gi");
+			findText = findText.replace( re,"\\$1" );
+		}
+		
+		var re = new RegExp(findText, reGlobal+reMatchCase+"m" ); //multiline
+		if(!re.exec(data)){
+			alert( "edit.findandreplace.nomatch".localize() );
+			return true;
+		}
+		
+		data = data.replace(re, replaceText);  
+	
+		this.storeTextarea();
+		if(!sel || (sel=="")){
+			this.textarea.value = data;
+		} else {
+			TextArea.replaceSelection( this.textarea, data );
+		}
+		if(this.textarea.onchange) this.textarea.onchange();
+	} ,
+			
+	/* TOOLBAR: cut/copy/paste clipboard functionality */
+	CLIPBOARD : null,
+	clipboard : function( format ){
+		var s = TextArea.getSelection(this.textarea);
+		if( !s || s == "") return;
+
+		this.CLIPBOARD = s ;
+		$( 'tbPASTE' ).className = this.ToolbarMarker;
+		var ss = format.replace( /\$/, s);
+		if( s == ss ) return; //copy
+
+		this.storeTextarea(); //cut
+		TextArea.replaceSelection( this.textarea, ss );
+	} ,
+
+	paste : function()
+	{
+		if( !this.CLIPBOARD ) return;
+		this.storeTextarea();
+		TextArea.replaceSelection( this.textarea, this.CLIPBOARD );
+	} ,
+
+	/* UNDO functionality: use by all toolbar and find&replace functions */
+	UNDOstack : [],
+	REDOstack : [],
+	UNDOdepth : 20,
+
+	storeTextarea : function() {
+		this.UNDOstack.push( this.textarea.value );
+		$('tbUNDO').disabled = '';
+		this.REDOstack = [];
+		$('tbREDO').disabled = 'true';
+		if(this.UNDOstack.length > this.UNDOdepth) this.UNDOstack.shift();
+	},
+	undoTextarea : function(){
+		if(this.UNDOstack.length > 0){
+			$('tbREDO').disabled = '';
+			this.REDOstack.push(this.textarea.value);
+			this.textarea.value = this.UNDOstack.pop();
+		}
+		if(this.UNDOstack.length == 0) $('tbUNDO').disabled = 'true';
+		if(!this.selector) return;
+		this.onSelectorLoad();
+		this.onSelectorChanged();
+
+		this.textarea.focus();
+	},	
+	redoTextarea : function(){
+		if(this.REDOstack.length > 0){
+			$('tbUNDO').disabled = '';
+			this.UNDOstack.push(this.textarea.value);
+			this.textarea.value = this.REDOstack.pop();
+		}
+		if(this.REDOstack.length == 0) $('tbREDO').disabled = 'true';
+		if(!this.selector) return;
+		this.onSelectorLoad();
+		this.onSelectorChanged();
+		this.textarea.focus();
+	},
+	getSuggestionMenu: function(){
+		var mID = 'findSuggestionMenu',
+			m = $(mID);
+		if( !m ) {
+			m = new Element('div',{'id':mID}).injectTop( $('favorites') );
+		}
+		return m;
+	}			  
+} 
+
+
+/** TextArea support routines
+ **/
+var TextArea =
+{
+	getSelection: function(id){
+		var f = $(id); if(!f) return ''; 
+		
+		if(window.ie) return document.selection.createRange().text;
+		return f.getValue().substring(f.selectionStart, f.selectionEnd);
+	},
+
+	/* replaces the selection with aValue, and returns with aValue selected */
+	replaceSelection: function(id, newText){
+		var f = $(id); if(!f) return;
+		var scrollTop = this.scrollTop;
+		 
+		if(window.ie){
+			f.focus();
+			var r = document.selection.createRange();
+			r.text = newText;
+			r.moveStart('character',-newText.length); /***/
+			r.select();
+			f.range.select();
+		}
+		else { 
+			var start = f.selectionStart, end = f.selectionEnd;
+			f.value = f.value.substring(0, start) + newText + f.value.substring(end);
+			f.replaceSelectionRange(start + newText.length, start + newText.length);
+		}
+		f.focus();
+		f.scrollTop = scrollTop;
+		if(f.onchange) f.onchange();
+	},
+	
+	/* check whether selection is preceeded by a \n (peek-ahead) */
+	isSelectionAtStartOfLine: function(id){
+		var f = $(id); if(!f) return false;
+
+		if(window.ie){
+			f.focus();
+			var r1 = document.selection.createRange(),
+				r2 = document.selection.createRange();
+			r2.moveStart( "character", -1);
+			if(r2.text=="") r2.moveEnd( "character", 1);
+			if(r1.compareEndPoints("StartToStart", r2) == 0) return true;
+			if(r2.text.charAt(0).match( /[\n\r]/ )) return true;
+		}
+		else {
+			if(f.selectionStart == 0) return true;
+			if(f.value.charAt(f.selectionStart-1) == '\n') return true;
+		} 
+		return false;
+	}	
+};
+
+//*************************
+// TODO
+// copied from default -- to be incorporated in EditTools
+var globalCursorPos; // global variabe to keep track of where the cursor was
+
+//sets the global variable to keep track of the cursor position
+function setCursorPos(id) 
+{
+	if(window.ie) return;
+	globalCursorPos = getCursorPos( $(id) );
+}
+
+function getCursorPos(textElement) 
+{
+	//save off the current value to restore it later,
+	var sOldText = textElement.value;
+
+	if(window.ie){
+		var objRange = document.selection.createRange(),
+			sOldRange = objRange.text,
+			sWeirdString = '#%~'; //small string that will not normally be encountered
+
+		//insert the weirdstring where the cursor is at
+		objRange.text = sOldRange + sWeirdString; 
+		objRange.moveStart('character', (0 - sOldRange.length - sWeirdString.length));
+
+		//save off the new string with the weirdstring in it
+		var sNewText = textElement.value;
+
+		//set the actual text value back to how it was
+		objRange.text = sOldRange;
+
+		//look through the new string we saved off and find the location of
+		//the weirdstring that was inserted and return that value
+		for (i=0; i <= sNewText.length; i++) {
+			var sTemp = sNewText.substring(i, i + sWeirdString.length);
+			if (sTemp == sWeirdString) {
+			  var cursorPos = (i - sOldRange.length);
+			  return cursorPos;
+			}
+		}
+	}
+	// Mozilla and the rest
+	else if( textElement.selectionStart || textElement.selectionStart == '0') 
+	{
+		return textElement.selectionStart;
+	}
+	else
+	{
+		return sOldText.length;
+	}
+}
+
+//this function inserts the input string into the textarea
+//where the cursor was at
+function insertString(stringToInsert) {
+	var firstPart = myForm.myTextArea.value.substring(0, globalCursorPos);
+	var secondPart = myForm.myTextArea.value.substring(globalCursorPos,
+			                                               myForm.myTextArea.value.length);
+	myForm.myTextArea.value = firstPart + stringToInsert + secondPart;
+} 
+
+/**
+ ** JSON-RPC
+ ** POST is
+ ** {"id": 2, "method": "search.getSuggestions", "params": ["p", 10]}
+ ** Response is
+ ** {"result":{"list":["Pic\/ruby.jpg","Pic\/telenet-smile.gif","Pic\/spin-greyblocks.gif","Pic\/shadow_transparent2.png","Pic\/monkey-mam-child.jpg","Pic\/brushed-button.jpg","Pic\/resizecursorv.png","Pic\/UserKeychainIcon.tiff","PrototypeJavascriptLibrary","Pizza Margerita"],"javaClass":"java.util.ArrayList"},"id":2}
+ **/
+function getSuggestions(id)
+{
+	if(window.ie) return;
+	var textNode = $(id),
+		val = textNode.value,
+		searchword;
+	
+	var pos = getCursorPos(textNode);
+	for( i = pos-1; i > 0; i-- ){
+		if( val.charAt(i) == ']' ) break;
+		if( val.charAt(i) == '[' && i < val.length-1 ) { searchword = val.substring(i+1,pos); break; }
+	}
+
+	if(searchword){
+		jsonrpc.search.getSuggestions(callback, searchword, 10);
+	} else {
+		EditTools.getSuggestionMenu().hide();
+	}
+}
+function callback(result, exception)
+{   
+	if(exception) { alert(exception.message); return; }
+	 
+	var menuNode = EditTools.getSuggestionMenu(), 
+		html = [];
+
+	result.list.each(function(el) { html.push('<li>'+el+'</li>'); });
+
+	menuNode.setHTML('<ul>',html.join(''),'</ul>').show();
+}
+
+window.addEvent('load', EditTools.onPageLoad.bind(EditTools) ); //edit only
\ No newline at end of file

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-prefs.js
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-prefs.js?rev=627259&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-prefs.js (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/webdocs/scripts/jspwiki-prefs.js Tue Feb 12 22:11:16 2008
@@ -0,0 +1,117 @@
+/**
+ ** jspwiki preferences support routines
+ ** based on brushedGroup.js
+ ** Dirk Frederickx Jun 07
+ ** Uses mootools library
+ **/
+
+var WikiGroup =
+{
+	MembersID   : "membersfield",
+	GroupTltID  : "grouptemplate",
+	GroupID     : "groupfield",
+	NewGroupID  : "newgroup",
+	GroupInfoID : "groupinfo",
+	CancelBtnID : "cancelButton",
+	SaveBtnID   : "saveButton",
+	CreateBtnID : "createButton",
+	DeleteBtnID : "deleteButton",
+	groups      : { "(new)": { members:"", groupInfo:"" } },
+	cursor      : null,
+	isEditOn    : false,
+	isCreateOn  : false,
+
+	putGroup: function(group, members, groupInfo, isSelected){
+		this.groups[group] = { members: members, groupInfo: groupInfo };
+
+		var g = $(this.GroupTltID);
+		var gg = g.clone().setHTML(group);
+		gg.id = '';
+		g.parentNode.appendChild(gg);
+		$(gg).show();
+
+		if(isSelected || !this.cursor) this.onMouseOverGroup(gg);
+	} ,
+
+	onMouseOverGroup: function(node){
+		if(this.isEditOn) return;
+		this.setCursor(node);
+
+		var g = this.groups[ (node.id == this.GroupID) ? "(new)": node.innerHTML ];
+		$(this.MembersID).value = g.members;
+		$(this.GroupInfoID).innerHTML = g.groupInfo;
+	} ,
+
+	setCursor: function(node){
+		if(this.cursor) $(this.cursor).removeClass('cursor');
+		this.cursor = $(node).addClass('cursor');
+	} ,
+
+	//create new group: focus on input field
+	onClickNew: function(){
+		if(this.isEditOn) return;
+
+		this.isCreateOn = true;
+		$(this.MembersID).value = "";
+		this.toggle();
+	} ,
+
+	//toggle edit status of Group Editor
+	toggle: function(){
+		this.isEditOn = !this.isEditOn; //toggle
+
+		$(this.MembersID  ).disabled =
+		$(this.SaveBtnID  ).disabled =
+		$(this.CreateBtnID).disabled =
+		$(this.CancelBtnID).disabled = !this.isEditOn;
+		var del = $(this.DeleteBtnID);
+		if(del) del.disabled = this.isCreateOn || !this.isEditOn;
+
+		if(this.isCreateOn) { $(this.CreateBtnID).toggle(); $(this.SaveBtnID).toggle() };
+
+		var newGrp  = $(this.NewGroupID),
+			members = $(this.MembersID);
+
+		if(this.isEditOn){
+			members.getParent().addClass("cursor");
+
+			newGrp.disabled = !this.isCreateOn;
+			if(this.isCreateOn) { newGrp.focus(); } else { members.focus(); }
+		} else {
+			members.getParent().removeClass("cursor");
+
+			if(this.isCreateOn){
+				this.isCreateOn = false;
+				newGrp.value = newGrp.defaultValue;
+				members.value = "";
+			}
+			newGrp.blur();
+			members.blur();
+			newGrp.disabled = false;
+		}
+	} ,
+
+	// submit form to create new group
+	onSubmitNew: function(form, actionURL){
+		var newGrp = $(this.NewGroupID);
+		if(newGrp.value == newGrp.defaultValue){
+			alert("group.validName".localize());
+			newGrp.focus();
+		} else this.onSubmit(form, actionURL);
+	} ,
+
+	// submit form: fill out actual group and members info
+	onSubmit: function(form, actionURL){
+		if(! this.cursor) return false;
+		var g = (this.cursor.id == this.GroupID) ? $(this.NewGroupID).value: this.cursor.innerHTML;
+
+		/* form.action = actionURL; -- doesn't work in IE */
+		form.setAttribute("action", actionURL) ;
+		form.group.value = g;
+		form.members.value = $(this.MembersID).value;
+		form.action.value = "save";
+
+		Wiki.submitOnce(form);
+		form.submit();
+	}
+}
\ No newline at end of file