blob: 6dc43e7aee651b1af628a706f64fe21d48b720f0 [file] [log] [blame]
/**
** 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