You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by br...@apache.org on 2013/08/02 20:37:55 UTC

svn commit: r1509803 [2/4] - in /jspwiki/trunk: ./ jspwiki-war/ jspwiki-war/src/main/config/wro/ jspwiki-war/src/main/java/org/apache/wiki/ jspwiki-war/src/main/scripts/ jspwiki-war/src/main/styles/ jspwiki-war/src/main/webapp/scripts/ jspwiki-war/src/...

Added: jspwiki/trunk/jspwiki-war/src/main/scripts/jspwiki-edit.js
URL: http://svn.apache.org/viewvc/jspwiki/trunk/jspwiki-war/src/main/scripts/jspwiki-edit.js?rev=1509803&view=auto
==============================================================================
--- jspwiki/trunk/jspwiki-war/src/main/scripts/jspwiki-edit.js (added)
+++ jspwiki/trunk/jspwiki-war/src/main/scripts/jspwiki-edit.js Fri Aug  2 18:37:54 2013
@@ -0,0 +1,817 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you 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.
+ */
+
+/**
+ ** Javascript routines to support JSPWiki Editing
+ ** since v.2.6.0
+ ** uses mootools v1.1
+ **
+ ** Needs jspwiki-common.js and mootools.js
+ ** EditTools object (main object)
+ ** - find&replace functionality : with regexp support
+ ** - included popup-pagelinks routines from Janne
+ **
+ ** TextArea object
+ **     Supports selections inside textarea, in ie and other browsers
+ **/
+var WikiSnippets =
+{
+	getSnippets: function(){
+	 	// FIXME: This is a kludge; should really insert a Date plugin or something.
+		var now = new Date();
+		var day = ((now.getDate() < 10) ? "0" + now.getDate() : now.getDate())
+		var month = ((now.getMonth() < 9) ? "0" + (now.getMonth()+1) : (now.getMonth()+1) )
+		var currentDate = now.getFullYear() + "-" + month + "-" + day;
+
+		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\n", ""]
+	},
+	"h2" : {
+		snippet:["!!","Heading 2 title", ""],
+		tab:["Heading 2 title\n", ""]
+	},
+	"h3" : {
+		snippet:["!","Heading 3 title", ""],
+		tab:["Heading 3 title\n", ""]
+	},
+	"dl" : {
+		snippet:[";","term:definition text", ""],
+		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+", "+currentDate,"\n"],
+		tab:[Wiki.UserName,currentDate,'']
+	},
+	/* TODO: how to insert the proper current date/timestamp, inline with the preferred time format */
+	/* TODO: Should be localized. */
+	"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 */
+
+	},
+	getSmartPairs: function(){
+		return {
+		'"' : '"',
+		'(' : ')',
+		'{' : '}',
+		'[' : ']',
+		'<' : '>',
+		"'" : { scope:{ "{{{":"}}}" }, pair:"'" }
+		}
+	}
+}
+
+/*
+ *
+ */
+var EditTools =
+{
+	onPageLoad: function(){
+
+		Wiki.onPageLoad(); //Wiki.onpageload should always run first, but seems not guaranteed on ie so let's do this for sure
+
+		window.onbeforeunload = (function(){
+			var ta = $('editorarea');
+			if(ta.value != ta.defaultValue) return "edit.areyousure".localize();
+		}).bind(this);
+
+		this.wikisnippets = WikiSnippets.getSnippets();
+		this.wikismartpairs = WikiSnippets.getSmartPairs();
+
+		this.mainarea = this.textarea = $('editorarea');
+		if(!this.textarea || !this.textarea.visible) return;
+
+		/* may insert a new this.textarea */
+		this.onPageLoadSectionEdit( );
+
+		//this.ta = new TextArea( this.textarea );
+		this.ta = TextArea.initialize( this.textarea ); //FIXME
+
+		this.onPageLoadResizeTextarea();
+		this.onPageLoadToolbar();
+
+		this.onPageLoadPostEditor();
+		this.onPageLoadPreview();
+
+		this.textarea
+			.addEvent('click', this.getSuggestions.bind(this))
+			.addEvent('keyup', this.getSuggestions.bind(this))
+			.addEvent('change', this.onChangeTextarea.bind(this))
+			.focus();
+
+		/* regularly refresh section-edit toc and sneak-preview */
+		this.textarea.fireEvent.periodical(3000,this.textarea,['change']);
+	},
+
+	/* add textarea resize drag bar */
+	onPageLoadResizeTextarea: function(){
+		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);
+			}
+		});
+	},
+
+	onPageLoadToolbar: function(){
+		$('tools').addClass('collapsebox-closed');
+		Collapsible.render('editform','');
+
+		$('tbREDO').addEvent('click', this.redo.bind(this) );
+		$('tbUNDO').addEvent('click', this.undo.bind(this) );
+		$('doreplace').addEvent('click', this.doReplace.bind(this) );
+		$$('#tools a.tool').addEvent('click',this.toggleSnippet.bind(this) );
+
+	},
+
+	doReplace: function(e){
+		new Event(e).stop();
+
+		var findText	= $('tbFIND').value,
+			replaceText	= $('tbREPLACE').value,
+			isRegExp	= $('tbREGEXP').checked,
+			reGlobal	= $('tbGLOBAL').checked ? 'g' : '',
+			reMatchCase	= $('tbMatchCASE').checked ? '' : 'i';
+
+		if(findText == '') return;
+
+		var sel = TextArea.getSelection(this.textarea),
+			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)){
+			Wiki.alert('edit.findandreplace.nomatch'.localize());
+			return;// true;
+		}
+		data = data.replace(re, replaceText);
+
+		this.store();
+		if(!sel || (sel=="")){
+			this.textarea.value = data;
+		} else {
+			TextArea.replaceSelection( this.textarea, data );
+		}
+		this.textarea.fireEvent('change');
+	},
+
+	onPageLoadPostEditor: function(){
+		if(window.ie) return;
+
+		$('toolextra').show();
+		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.store();
+			this.element.value = value.join('');
+			this.element.fireEvent('change');
+		};
+
+		/* quick dirty patch: backspace should remove only one char and not 4 spaces */
+		this.posteditor.onBackspace=function(e) {
+		    var ss = this.ss(), se = this.se();
+    		if(ss == se && this.slice(ss - this.tabl,ss) == this.tab) {
+				return;
+			/*
+				e.preventDefault();
+				var start = this.getStart(this.tab), end = this.slice(ss,this.element.value.length);
+				if(start.match(/\n$/g) && end.match(/^\n/g)) {
+        			this.value([start,this.slice(ss-1,this.element.value.length)]);
+				} else {
+			  		this.value([start,end]);
+				}
+		  		this.selectRange(ss - this.tabl,0);
+		  	*/
+			} else if(ss == se) {
+  		  		var charCode  = this.slice(ss - 1,ss),
+  		      	close     = this.slice(ss,ss+1),
+  		      	stpair    = this.options.smartTypingPairs[charCode];
+  		  		if($type(stpair) == 'string') stpair = { pair : stpair };
+  		  		if(stpair && stpair.pair == close) {
+  		    		this.value([this.getStart(stpair.pair),this.slice(ss,this.element.value.length)]);
+          			this.selectRange(ss,0);
+  		  		}
+  			}
+  		};
+
+
+		/* next extra fix for latest Safari 3.1 cause tabs are not catched anymore in the onkeypress handler */
+		/* TODO: this could be a great workaround for ie as well */
+		if(window.webkit){
+			this.textarea.addEvent('keydown',function(e){
+				if(e.keyCode == 9) EditTools.posteditor.onKeyPress(e);
+			});
+		};
+
+		['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);
+
+		this.initPostEditor();
+	},
+
+	initPostEditor: function(){
+		if(! this.posteditor) return;
+		this.posteditor.changeSmartTypingPairs( $('smartpairs').checked ? this.wikismartpairs : {} );
+		this.posteditor.changeSnippets( $('tabcompletion').checked ? this.wikisnippets : {} );
+	},
+
+	toggleSnippet: function(e) {
+		e = new Event(e).stop();
+
+		var el  = e.target,
+			snippy = this.wikisnippets[el.getText()];
+
+		if(!snippy) return;
+
+		var s = TextArea.getSelection(this.textarea),
+			sn1 = snippy.snippet[0],
+			sn2 = snippy.snippet[2],
+			t = snippy.snippet.join('');
+
+		this.store();
+
+		if((el.rel=='break') && (!TextArea.isSelectionAtStartOfLine(this.textarea))) {
+			t = '\n' + t;
+		}
+		if(s) {
+			// toggle markup
+			if((s.indexOf(sn1) == 0) && (s.lastIndexOf(sn2) == (s.length - sn2.length))) {
+				t = s.substring(sn1.length, s.length-sn2.length);
+			} else {
+				t = t.replace( snippy.tab[0], s)
+			}
+		}
+		TextArea.replaceSelection(this.textarea, t);
+	} ,
+
+	// *** UNDO functionality ***
+	$undo: [],
+	$redo: [],
+	$maxundo: 20,
+
+	$get: function() {
+		var ta = this.textarea,
+			sel = TextArea.getSelectionCoordinates(ta);
+		return {
+			main:this.mainarea.value,
+			value:ta.value,
+			cursor:sel,
+			scrollTop:ta.scrollTop,
+			scrollLeft:ta.scrollLeft
+		};
+	},
+	$put: function(o){
+		var ta = this.textarea;
+		this.mainarea.value = o.main;
+		ta.value = o.value;
+		ta.scrollTop = o.scrollTop;
+		ta.scrollLeft = o.scrollLeft;
+		TextArea.setSelection(o.cursor.start,o.cursor.end);
+		ta.fireEvent('change');
+	},
+	store: function() {
+		this.$undo.push( this.$get() );
+		this.$redo = [];
+		if(this.$undo.length > this.$maxundo) this.$undo.shift();
+
+		$('tbUNDO').disabled = '';
+		$('tbREDO').disabled = 'true';
+	},
+	undo: function(e){
+		new Event(e).stop();
+		if(this.$undo.length > 0){
+			$('tbREDO').disabled = '';
+			this.$redo.push( this.$get() );
+			this.$put( this.$undo.pop() );
+		} else {
+			$('tbUNDO').disabled = 'true';
+		}
+	},
+	redo: function(e){
+		new Event(e).stop();
+		if(this.$redo.length > 0){
+			this.$undo.push( this.$get() );
+			this.$put( this.$redo.pop() );
+			$('tbUNDO').disabled = '';
+		} else {
+			$('tbREDO').disabled = 'true';
+		}
+	},
+	// *** end of UNDO functionality ***
+
+	getSuggestions: function() {
+		var textarea = this.textarea,
+			sel = TextArea.getSelectionCoordinates(textarea),
+			val = textarea.value,
+			searchword = '',
+			searchlen = 0;
+
+		var	suggestID = 'findSuggestionMenu',
+			suggest = $(suggestID) || new Element('div',{
+				'id':suggestID
+			}).injectAfter($('favorites').getFirst());
+
+		/* find a partial jspwiki-link 'searchword' */
+		/* look backwards for the start of a wiki-link bracket */
+		for( var i = sel.start-1; i >= 0; i-- ){
+			if( val.charAt(i) == ']' ) break;
+			if( val.charAt(i) == '[' && i < val.length-1 ) {
+				searchword = val.substring(i+1,sel.start);
+                if( searchword.charAt(0) == '{' ) return; // Ignore plugins.
+				if(searchword.indexOf('|') != -1) searchword = searchword.split('|')[1];
+				searchlen = searchword.length;
+
+				if(searchlen == 0) searchword=Wiki.PageName+'/'; /* by default - get list of attachments, if any */
+				break;
+			}
+		}
+		if(searchword =='') return suggest.hide();
+
+		if(sel.start == sel.end) { //when no selection, extend till next ]  or end of the line
+			var ss = val.substring(sel.start),
+				end = ss.search(/[\n\r\]]/);
+			if(end!=-1) sel.end = sel.start+end;
+		}
+
+		Wiki.jsonrpc('search.getSuggestions', [searchword,30], function(result,exception){
+			if(exception) {
+				alert(exception.message);
+			} else if(!result.list || (result.list.length == 0)) {
+				suggest.hide();
+			} else {
+				var ul = new Element('ul').inject( suggest.empty().show() );
+				result.list.each( function(rslt) {
+					new Element('li',{
+						'title':rslt,
+						'events': {
+							'click':function(ev){
+								new Event(ev).stop();
+								EditTools.store();
+								TextArea.setSelection(sel.start,sel.end);
+								TextArea.replaceSelection(textarea, rslt.substr(searchlen));
+								sel.end = sel.start + rslt.length - searchlen;
+							},
+							'mouseout': function(){ this.removeClass('hover') },
+							'mouseover':function(){ this.addClass('hover') }
+						}
+					}).setHTML(rslt.trunc(36) ).inject(ul);
+				}); /* each */
+			} /* endif */
+		});
+	},
+
+	onPageLoadPreview : function(){
+		var checkbox = $('autopreview');
+
+		if(!checkbox) return;
+
+		checkbox
+			.setProperty('checked', Wiki.prefs.get('autopreview') || false)
+			.addEvent('click', function(){
+				var ta = this.textarea,
+					isOn = checkbox.checked;
+
+				$('sneakpreview').empty();
+				ta.removeEvents('preview');
+				Wiki.prefs.set('autopreview',isOn);
+
+				if(isOn) ta.addEvent('preview', this.refreshPreview.bind(this)).fireEvent('preview');
+
+			}.bind(this)).fireEvent('click');
+    },
+
+	refreshPreview: function(){
+    	var	preview = $('sneakpreview');
+
+		$('previewSpin').show();
+		new Ajax( Wiki.TemplateUrl + "/AJAXPreview.jsp?page="+Wiki.PageName, {
+			postBody: 'wikimarkup=' + encodeURIComponent(this.textarea.value),
+			update: preview,
+			onComplete: function(){
+				$('previewSpin').hide();
+				Wiki.renderPage(preview, Wiki.PageName);
+			}
+		}).request();
+	},
+
+	onPageLoadSectionEdit : function( ){
+
+		/* section editing is only valid for edit context, not valid in the comment context */
+		if( (Wiki.Context!='edit')
+		  ||(Wiki.prefs.get('SectionEditing') != 'on') ) return;
+
+		/* Duplicate the textarea into a main and work area.
+		   The workarea is used for actual editing.
+		   The mainarea reflects at all times the whole document
+		*/
+		this.textarea = this.mainarea.clone()
+			.removeProperty('id')
+			.removeProperty('name')
+			.injectBefore( this.mainarea.hide() );
+
+		var tt = new Element('div',{'id':'toctoc'}).adopt(
+			new Element('label').setHTML('sectionediting.label'.localize()),
+			this.sections = new Element('ul')
+		).injectTop($('favorites'))
+
+		/* initialise the section sections */
+		this.onSectionLoad();
+
+		var cursor = location.search.match(/[&?]section=(\d+)/);
+		cursor = (cursor && cursor[1]) ? 1+cursor[1].toInt() : 0;
+		if((cursor>0) && this.textarea.sop) cursor++;
+
+		/* initialise the selected section */
+		this.onChangeSection(cursor);
+
+	},
+
+	/*
+	 * UPDATE/RFEFRESH the section dropdown
+	 * This function is called at startup, and everytime the section textarea changes
+	 * Postcondition: the section-edit dropdown contains following entries
+	 *   0. ( all )
+	 *   1. start-of-page (if applicable)
+	 *   2. text==<<header 1...n>> , <<sections.offset stores start-offset in main textarea>>
+	 */
+	 onSectionLoad : function(){
+		var mainarea = this.mainarea.value,
+			ta = this.textarea,
+			DELIM = "\u00a4";
+
+		/* mask all headers inside a {{{ ... }}} but keep length unchanged! */
+		mainarea = mainarea.replace(/\{\{\{([\s\S]*?)\}\}\}/g, function(match){
+			return match.replace( /^!/mg, ' ' );
+		});
+
+		var tt = mainarea.replace( /^([!]{1,3})/mg, DELIM+"$1"+DELIM ).split(DELIM);
+
+		this.newSection();
+		ta.sop = (tt.length>1) && (tt[0] != ''); //start of page section has no !!!header
+		if(ta.sop) this.addSection("edit.startOfPage".localize(), 0, 0);
+
+		var pos = tt.shift().length,
+			ttlen = tt.map(function(i){ return i.length });
+
+		for(var i=0; i<ttlen.length; i=i+2){
+			var hlen = ttlen[i],  //length of header markup !!!,!! or !
+				indent = (hlen==2) ? 1 : (hlen==1) ? 2 : 0,
+				title = tt[i+1].match(/.*?$/m)[0]; //title is first line only
+
+			this.addSection(title, pos, indent);
+			pos += hlen + ttlen[i+1];
+		};
+	},
+
+	setSection: function( cursor ){
+		var els = this.sections.getChildren();
+
+		if(cursor <0 || cursor >= els.length) cursor = 0;
+		els.removeClass('cursor');
+		els[cursor].addClass('cursor');
+	},
+
+	newSection: function(){
+		this.sections.empty();
+		this.sections.offsets = [];
+		this.addSection("edit.allsections".localize(),-1,0);
+	},
+
+	addSection: function(text,offset,indent){
+		text = text.replace(/~([^~])/g, '$1'); /*remove wiki-markup escape chars ~ */
+		this.sections.offsets.push(offset);
+		this.sections.adopt(
+			new Element('li').adopt(
+				new Element('a',{
+					'class':'action',
+					'styles': {
+						'padding-left':(indent+0.5)+'em'
+					},
+					'title':text,
+					'events':{
+						'click':this.onChangeSection.pass([this.sections.offsets.length-1],this)
+					}
+				}).setHTML(text.trunc(30))
+			)
+		);
+	},
+
+	/* the USER clicks a new item from the section dropdown
+	 * copy a part of the main textarea to the section textarea
+	 */
+	onChangeSection: function(cursor){
+		var se = this.sections.offsets,
+			ta = this.textarea,
+			ma = this.mainarea.value;
+
+		this.setSection(cursor);
+		ta.cursor = cursor;
+		ta.begin = (cursor==0) ? 0 : se[cursor];
+		ta.end = ((cursor==0) || (cursor+1 >= se.length)) ? ma.length : se[cursor+1];
+		ta.value = ma.substring(ta.begin,ta.end);
+		ta.focus();
+		ta.fireEvent('preview');
+	},
+
+	/*
+	 * Changes in the section textarea:
+	 * happens when
+	 *  (i)  textarea is changed and deselected (click outside the textarea)
+	 *  (ii) user clicks a toolbar-button
+	 *  (iii) periodical
+	 *
+	 * 1) copy section textarea at the right offset of the main textarea
+	 * 2) refresh the section-edit menu
+	 */
+	onChangeTextarea : function(){
+		var	ta = this.textarea,	ma = this.mainarea;
+
+		if(ta.value == this.cacheTextarea) return;
+		this.cacheTextarea = ta.value;
+
+		if( this.sections ){
+			var	s = ma.value,
+				//insert \n to ensure the next line's !!!header remains at column 0.
+				addNewLine = ((ta.value.slice(-1) != '\n')  && (s.charAt(ta.end) =='!')) ? '\n' : '';
+
+			ma.value = s.substring(0, ta.begin) + ta.value + addNewLine + s.substring(ta.end);
+			ta.end = ta.begin + ta.value.length;
+			this.onSectionLoad();  //refresh section-edit menu
+		}
+		ta.fireEvent('preview');
+	 }
+}
+
+/*
+ * TextArea support routines
+ */
+//var TextArea = new Class({
+var TextArea =
+{
+	initialize: function(el){
+		this.textarea = $(el);
+		return this;
+	},
+
+	getSelection: function(id){
+		var f = $(id); if(!f) return '';
+
+		// IE fixme: this returns any selection, not only selected text in the textarea
+		//if(window.ie) return document.selection.createRange().text;
+		//return f.getValue().substring(f.selectionStart, f.selectionEnd);
+
+		var cur = this.getSelectionCoordinates(id);
+		return f.getValue().substring(cur.start, cur.end);
+	},
+
+	/*
+	Function: setSelectionRange
+		Selects the selection range of the textarea from start to end
+
+	Arguments:
+		start - start position of the selection
+		end - (optional) end position of the seletion (default == start)
+
+	Returns:
+		Textarea object
+	*/
+	setSelection: function(start, end){
+
+		var txta = this.textarea;
+		if(!end) end = start;
+
+		if($defined(txta.setSelectionRange)){
+
+			txta.setSelectionRange(start, end);
+
+		} else {
+
+            var value = txta.value,
+            	diff = value.substr(start, end - start).replace(/\r/g, '').length;
+
+            start = value.substr(0, start).replace(/\r/g, '').length;
+
+			var range = txta.createTextRange();
+			range.collapse(true);
+			range.moveEnd('character', start + diff);
+			range.moveStart('character', start);
+			range.select();
+			//textarea.scrollTop = scrollPosition;
+			//textarea.focus();
+
+		}
+		return this;
+
+	},
+
+	// getCursor(id) : returns start offset of cursor (integer)
+	getCursor: function(id) {
+		return this.getSelectionCoordinates(id).start;
+	},
+
+	/*
+	Function: getSelectionCoordinates
+		Returns the selected textarea range.
+
+	Returns:
+		{{ { 'start':number, 'end':number, 'thin':boolean } }}
+		start - coordinate of the selection
+		end - coordinate of the selection
+		thin - boolean indicates whether selection is empty (start==end)
+	*/
+	getSelectionCoordinates: function(id) {
+		var txta = $(id),
+			pos = {start: 0, end: 0, thin: true};
+
+		if( $defined(txta.selectionStart) ){
+
+			pos = { start: txta.selectionStart, end: txta.selectionEnd };
+
+		} else {
+
+	  		var range = document.selection.createRange();
+			if (!range || range.parentElement() != txta) return pos;
+	 		var dup = range.duplicate(),
+				value = txta.value,
+				offset = value.length - value.match(/[\n\r]*$/)[0].length;
+
+			dup.moveToElementText(txta);
+			dup.setEndPoint('StartToEnd', range);
+			pos.end = offset - dup.text.length;
+	  		dup.setEndPoint('StartToStart', range);
+			pos.start = offset - dup.text.length;
+
+		}
+
+		pos.thin = (pos.start==pos.end);
+		return pos;
+
+	},
+
+	// replaceSelection(id,newtext) replaces the selection with a newtext, an selects the replaced newtext
+	replaceSelection: function(id, newText){
+
+		var value = newText.replace(/\r/g, ''), //$A(arguments).join(''),
+			txta = $(id),
+			scrollTop = txta.scrollTop; //cache top
+
+		if( $defined(txta.selectionStart) ){
+
+			var start = txta.selectionStart,
+				end = txta.selectionEnd,
+				v = txta.value;
+			txta.value = v.substr(0, start) + value + v.substr(end);
+			txta.selectionStart = start;
+			txta.selectionEnd = start + value.length;
+
+		} else {
+
+			txta.focus();
+			var range = document.selection.createRange();
+			range.text = value;
+			range.collapse(true);
+			range.moveStart("character", -value.length);
+			range.select();
+
+		}
+		txta.focus();
+		txta.scrollTop = scrollTop;
+		txta.fireEvent('change');
+		return;
+
+	},
+
+	// isSelectionAtStartOfLine(id): returns boolean indicating whether cursor is at the start of newline
+	isSelectionAtStartOfLine: function(id){
+		var f = $(id); if(!f) return false;
+
+		var i = this.getCursor(id);
+	    return( (i<=0) || ( f.value.charAt( i-1 ).match( /[\n\r]/ ) ) );
+	}
+
+};
+
+window.addEvent('load', EditTools.onPageLoad.bind(EditTools) );
\ No newline at end of file

Added: jspwiki/trunk/jspwiki-war/src/main/scripts/jspwiki-prefs.js
URL: http://svn.apache.org/viewvc/jspwiki/trunk/jspwiki-war/src/main/scripts/jspwiki-prefs.js?rev=1509803&view=auto
==============================================================================
--- jspwiki/trunk/jspwiki-war/src/main/scripts/jspwiki-prefs.js (added)
+++ jspwiki/trunk/jspwiki-war/src/main/scripts/jspwiki-prefs.js Fri Aug  2 18:37:54 2013
@@ -0,0 +1,164 @@
+/* 
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you 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.  
+ */
+ 
+/**
+ ** Javascript routines to support JSPWiki UserPreferences
+ ** since v.2.6.0
+ ** uses mootools v1.1
+ **/
+
+var WikiPreferences =
+{
+	onPageLoad: function(){
+
+		window.onbeforeunload = (function(){
+
+			if( $('prefs').getFormElements().some(function(el){
+				return (el.getValue() != el.getDefaultValue());  
+			}) ) return "prefs.areyousure".localize();
+
+		}).bind(this);
+ 	},
+ 	
+	savePrefs: function(){
+		var prefs = {
+			'prefSkin':'SkinName',
+			'prefTimeZone':'TimeZone',
+			'prefTimeFormat':'DateFormat',
+			'prefOrientation':'Orientation',
+			'editor':'editor',
+			'prefLanguage':'Language',
+			'prefSectionEditing':'SectionEditing'
+		};
+		for(var el in prefs){
+			if($(el)) Wiki.prefs.set(prefs[el],$(el).getValue());
+		};
+	}
+}
+
+window.addEvent('load', WikiPreferences.onPageLoad.bind(WikiPreferences) );
+
+// refactor me
+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 = $("grouptemplate");
+			gg = g.clone().removeProperty('id').setHTML(group).inject(g.getParent()).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

Added: jspwiki/trunk/jspwiki-war/src/main/scripts/posteditor.js
URL: http://svn.apache.org/viewvc/jspwiki/trunk/jspwiki-war/src/main/scripts/posteditor.js?rev=1509803&view=auto
==============================================================================
--- jspwiki/trunk/jspwiki-war/src/main/scripts/posteditor.js (added)
+++ jspwiki/trunk/jspwiki-war/src/main/scripts/posteditor.js Fri Aug  2 18:37:54 2013
@@ -0,0 +1 @@
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('k 1K={};1K.2q=2h 2a({r:"    ",22:q(G){b.G=1G.12({Z:{},X:{},1g:{}},G||{})},2n:q(1U,N,G){d(2g.2f)t;b.v=$(1U);b.N=$(N);b.22(G);b.15={1a:b.v.1u(\'1t-W\').1q()||14,2p:b.v.1u(\'2o-2m\').1q()||11,W:b.v.1u(\'W\').1q()};b.h=B;b.I=0;b.M=0;b.u=B;b.K=b.r.f;b.v.2e=b.1O.2c(b)},2b:q(Z){b.G.Z=Z||{}},29:q(X){b.G.X=X||{}},28:q(1g){b.G.1g=1g||{}},c:q(){t b.v.1D},j:q(){t b.v.1B},y:q(l,E){t b.v.o.y(l,E)},o:q(o){b.v.o=o.Y("")},F:q(O){k O=O?O.f:0;t b.y(0,b.c()-O)},J:q(O){k O=O?O.f:0;t b.v.o.y(b.j()-O)},w:q(l,E){b.v.1D=l;b.v.1B=l+E},S:q(S,P){d(P){b.10=b.v.10;b.1k=b.v.1k}z{b.v.10=b.10;b.v.1k=b.1k}d(S)b.v.S()},1Z:q(){k 1X=b.F().1j("\\n").f,W=(1X-2l.2k(b.v.10/b.15.1a))*b.15.1a;W+=b.15.1a;d
 (W>=b.15.W)b.v.10+=b.15.1a;b.S(H,1)},1O:q(e){d(b.1S(e))t;d(b.1R(e))t;b.1Q(e);d(b.1P(e))t;d([13,9,8,1w].1c(e.1b))b.S(R,H);2d(e.1b){18 27:b.u=B;b.h=B;T;18 1N:b.1M(e);T;18 13:b.1L(e);T;18 9:b.1v(e);T;18 8:b.1I(e);T;18 1w:b.1H(e);T}d([13,9,8,1w].1c(e.1b))b.S(H,R)},1S:q(e){d(e.1F&&e.1b==13){d(b.N){e.U();b.N.S();t H}}t R},1R:q(e){k L=1E.1J(e.L),C=b.G.X[L];d(C){d($P(C)==\'1C\')C={16:C};d(!C.V||b.V(C.V)){k c=b.c(),j=b.j(),l=b.F();d(c==j){b.o([l,C.16,b.J()]);b.w(l.f,0)}z{e.U();b.I=c;b.M=j;b.o([l,L,b.y(c,j),C.16,b.J()]);b.w(c+1,j-c)}}C=B;t H}t R},1Q:q(e){k L=1E.1J(e.L);d(e.26&&e.1F){d([0,1,2,3,4,5,6,7,8,9].1c(L)){k 1s=b.G.1g[L];d(1s){k c=b.c(),j=b.j(),x=1s.1A(b,[b.y(c,j)]);d(x){k l=b.F();d($P(x)==\'1r\'){b.o([l,x.Y(""),b.J()]);b.w(l.f+x[0].f,x[1].f)}z{d(x.19){d(x.p){l=b.y(0,x.19[0]);k E=b.y(x.19[1],b.v.o.f);b.o([l,x.p.Y(""),E]);b.w(l.f+x.p[0].f,x.p[1].f)}z{b.w(x.19[0],x.19[1])}}z{b.o([l,x.p.Y(""),b.J()]);b.w(l.f+x.p[0].f,x.p[1].f)}}}}}}},1P:q(e){d(b.h){k c=b.c(),j=b.j(),D=b.I,E=b.M;d(![D+1,D,
 D-1,E].1c(c)){b.u=B;b.h=B}d(b.h&&[25,1N].1c(e.1b)&&c==j){b.u=B;b.h=B}b.I=c;b.M=j}z{b.I=0;b.M=0}t R},V:q(1p){k c=b.c(),Q=b.F();1z(k D 1y 1p){d(!D)t H;k 1n=Q.1o(D);d(1n>-1){k 1m=b.y(1n+D.f,c).1o(1p[D]);d(1m==-1)t H}}t R},1M:q(e){k c=b.c(),j=b.j(),l=b.F();d(c!=j){e.U();b.w(j,0)}},1L:q(e){b.1Z();k c=b.c(),j=b.j(),l=b.F();d(c==j){k 1t=l.1j("\\n").23(),r=1t.1l(/^\\s+/21);d(r){e.U();r=r.Y("");b.o([l,"\\n",r,b.J()]);b.w(c+1+r.f,0)}}},1I:q(e){k c=b.c(),j=b.j();d(c==j&&b.y(c-b.K,c)==b.r){e.U();k l=b.F(b.r),E=b.y(c,b.v.o.f);d(l.1l(/\\n$/g)&&E.1l(/^\\n/g)){b.o([l,b.y(c-1,b.v.o.f)])}z{b.o([l,E])}b.w(c-b.K,0)}z d(c==j){k L=b.y(c-1,c),1m=b.y(c,c+1),C=b.G.X[L];d($P(C)==\'1C\')C={16:C};d(C&&C.16==1m){b.o([b.F(C.16),b.y(c,b.v.o.f)]);b.w(c,0)}}},1H:q(e){k c=b.c(),j=b.j();d(c==j&&b.y(c,c+b.K)==b.r){e.U();b.o([b.F(),b.y(c+b.K,b.v.o.f)]);b.w(c,0)}},1v:q(e){e.U();k c=b.c(),j=b.j(),x=b.y(c,j),Q=b.F();d(b.20(e,c,j))t;d(b.24(e,c,j))t;d(c!=j&&x.1i("\\n")!=-1){k 1Y=x.1h(/\\n/g,"\\n"+b.r);b.o([Q,b.r,1Y,b.J()]);
 b.w(c+b.K,j+(b.K*x.1j("\\n").f)-c-b.K)}z{k A=B;1z(k D 1y b.G.Z){k o=b.G.Z[D];d($P(o)==\'q\')1W;d(Q.f-D.f==-1)1W;d(Q.f-D.f==Q.1o(D)){d($P(o)==\'1r\')o={p:o};A=1G.12({},o);T}}d(A&&(!A.V||b.V(A.V))){d(A.17){k 17=A.17.1A(b,[D]);d($P(17)==\'1r\')A.p=17;z A=17}k p=A.p.1f(),r=Q.1j("\\n").23().1l(/^\\s+/21),l=b.F(A.D||D);d(r){r=r.Y("");p[0]=p[0].1h(/\\n/g,"\\n"+r);p[1]=p[1].1h(/\\n/g,"\\n"+r);p[2]=p[2].1h(/\\n/g,"\\n"+r)}b.o([l,p[0],p[1],p[2],b.J()]);d(A.r){b.h={r:A.r.1f(),p:p.1f(),l:A.l};k m=b.h.r.1V();b.h.c=p[1].1i(m);d(b.h.c>-1){b.h.1e=l.f+p[0].f+b.h.c;b.I=b.h.1e;b.M=b.I+m.f;b.u=B;d(A.u){b.h.u=A.u;b.h.m=m;b.h.1d=H;d(2j A.1d==\'2i\')b.h.1d=A.1d;k u=b.h.u[m];d(u){k i=[m].12(u);k a=u.1f().12([\'\']);b.h.1x=m;b.u=a.1T(i)}}b.w(l.f+p[0].f+b.h.c,m.f)}z{b.h=B;b.w(l.f+p[0].f,p[1].f)}}z{b.w(l.f+p[0].f,p[1].f)}p=B}z{b.o([Q,b.r,b.y(c,b.v.o.f)]);d(c==j)b.w(c+b.K,0);z b.w(c+b.K,j-c)}}},24:q(e,c,j){d(b.h){k f=b.h.r.f;d(f){d(b.h.1e<=c){k m=b.h.r.1V(),N=b.y(c,c+b.h.p[1].f-b.h.c).1i(m);d(f==1&&!m){k E=b.h
 .p[2].f;d($P(b.h.l)==\'2r\')E=b.h.l;z d(b.h.l)E=0;b.w(j+b.J().1i(b.h.p[2])+E,0);b.u=B;t H}z d(N>-1){b.h.c=N;b.h.1e=N+c;b.I=b.h.1e;b.M=b.I+m.f;b.h.m=m;d(b.u){k u=b.h.u[m];d(u){k i=[m].12(u);k a=u.1f().12([\'\']);b.h.1x=m;b.u=a.1T(i)}z{b.u=B}}b.w(c+N,m.f);t H}z{b.1v(e);t H}}}b.h=B}t R},20:q(e,c,j){d(b.u&&c==b.I&&j==b.M&&b.h.m.f==j-c){k m=b.u[b.h.m];d(m){b.M=b.I+m.f;b.h.m=m;b.o([b.F(),m,b.J()]);b.w(c,m.f);t H}z d(b.h.1d){m=b.h.1x;b.h.m=m;b.M=b.I+m.f;b.o([b.F(),m,b.J()]);b.w(c,m.f);t H}}b.u=B;t R}});',62,152,'|||||||||||this|ss|if||length||autoTab||se|var|start|item||value|snippet|function|tab||return|completion|element|selectRange|sel|slice|else|snippetObj|null|stpair|key|end|getStart|options|true|ssKey|getEnd|tabl|charCode|seKey|next|rest|type|text|false|focus|break|preventDefault|scope|height|smartTypingPairs|join|snippets|scrollTop||extend|||styles|pair|command|case|selection|line_height|keyCode|test|loop|ssLast|copy|selections|replace|indexOf|split|scrollLeft|match|close|open|lastI
 ndexOf|scopes|toInt|array|fn|line|getStyle|onTab|46|index|in|for|apply|selectionEnd|string|selectionStart|String|shiftKey|Object|onDelete|onBackspace|fromCharCode|postEditor|onEnter|onKeyRight|39|onKeyPress|filterByTab|filterBySelect|filterByPairs|filterByNext|associate|el|shift|continue|lines|newsel|updateScroll|filterCompletion|gi|setOptions|pop|filterAutoTab|38|ctrlKey||changeSelections|changeSmartTypingPairs|Class|changeSnippets|bind|switch|onkeypress|ActiveXObject|window|new|boolean|typeof|round|Math|size|initialize|font|font_size|create|number'.split('|'),0,{}))
\ No newline at end of file