1/** 2 * Functions for text editing (toolbar stuff) 3 * 4 * @todo I'm no JS guru please help if you know how to improve 5 * @author Andreas Gohr <andi@splitbrain.org> 6 */ 7 8/** 9 * Creates a toolbar button through the DOM 10 * 11 * Style the buttons through the toolbutton class 12 * 13 * @author Andreas Gohr <andi@splitbrain.org> 14 */ 15function createToolButton(icon,label,key,id){ 16 var btn = document.createElement('button'); 17 var ico = document.createElement('img'); 18 19 // preapare the basic button stuff 20 btn.className = 'toolbutton'; 21 btn.title = label; 22 if(key){ 23 btn.title += ' [ALT+'+key.toUpperCase()+']'; 24 btn.accessKey = key; 25 } 26 27 // set IDs if given 28 if(id){ 29 btn.id = id; 30 ico.id = id+'_ico'; 31 } 32 33 // create the icon and add it to the button 34 ico.src = DOKU_BASE+'lib/images/toolbar/'+icon; 35 btn.appendChild(ico); 36 37 return btn; 38} 39 40/** 41 * Creates a picker window for inserting text 42 * 43 * The given list can be an associative array with text,icon pairs 44 * or a simple list of text. Style the picker window through the picker 45 * class or the picker buttons with the pickerbutton class. Picker 46 * windows are appended to the body and created invisible. 47 * 48 * @author Andreas Gohr <andi@splitbrain.org> 49 */ 50function createPicker(id,list,icobase,edid){ 51 var cnt = list.length; 52 53 var picker = document.createElement('div'); 54 picker.className = 'picker'; 55 picker.id = id; 56 picker.style.position = 'absolute'; 57 picker.style.display = 'none'; 58 59 for(var key in list){ 60 var btn = document.createElement('button'); 61 62 btn.className = 'pickerbutton'; 63 64 // associative array? 65 if(isNaN(key)){ 66 var ico = document.createElement('img'); 67 ico.src = DOKU_BASE+'lib/images/'+icobase+'/'+list[key]; 68 btn.title = key; 69 btn.appendChild(ico); 70 eval("btn.onclick = function(){pickerInsert('"+id+"','"+ 71 jsEscape(key)+"','"+ 72 jsEscape(edid)+"');return false;}"); 73 }else{ 74 var txt = document.createTextNode(list[key]); 75 btn.title = list[key]; 76 btn.appendChild(txt); 77 eval("btn.onclick = function(){pickerInsert('"+id+"','"+ 78 jsEscape(list[key])+"','"+ 79 jsEscape(edid)+"');return false;}"); 80 } 81 82 picker.appendChild(btn); 83 } 84 var body = document.getElementsByTagName('body')[0]; 85 body.appendChild(picker); 86} 87 88/** 89 * Called by picker buttons to insert Text and close the picker again 90 * 91 * @author Andreas Gohr <andi@splitbrain.org> 92 */ 93function pickerInsert(pickerid,text,edid){ 94 // insert 95 insertAtCarret(edid,text); 96 // close picker 97 pobj = document.getElementById(pickerid); 98 pobj.style.display = 'none'; 99} 100 101/** 102 * Show a previosly created picker window 103 * 104 * @author Andreas Gohr <andi@splitbrain.org> 105 */ 106function showPicker(pickerid,btn){ 107 var picker = document.getElementById(pickerid); 108 var x = findPosX(btn); 109 var y = findPosY(btn); 110 if(picker.style.display == 'none'){ 111 picker.style.display = 'block'; 112 picker.style.left = (x+3)+'px'; 113 picker.style.top = (y+btn.offsetHeight+3)+'px'; 114 }else{ 115 picker.style.display = 'none'; 116 } 117} 118 119/** 120 * Create a toolbar 121 * 122 * @param string tbid ID of the element where to insert the toolbar 123 * @param string edid ID of the editor textarea 124 * @param array tb Associative array defining the buttons 125 * @author Andreas Gohr <andi@splitbrain.org> 126 */ 127function initToolbar(tbid,edid,tb){ 128 if(!document.getElementById){ return; } 129 var toolbar = document.getElementById(tbid); 130 var cnt = tb.length; 131 for(var i=0; i<cnt; i++){ 132 // create new button 133 btn = createToolButton(tb[i]['icon'], 134 tb[i]['title'], 135 tb[i]['key']); 136 137 // add button action dependend on type 138 switch(tb[i]['type']){ 139 case 'format': 140 var sample = tb[i]['title']; 141 if(tb[i]['sample']){ sample = tb[i]['sample']; } 142 143 eval("btn.onclick = function(){insertTags('"+ 144 jsEscape(edid)+"','"+ 145 jsEscape(tb[i]['open'])+"','"+ 146 jsEscape(tb[i]['close'])+"','"+ 147 jsEscape(sample)+ 148 "');return false;}"); 149 toolbar.appendChild(btn); 150 break; 151 case 'insert': 152 eval("btn.onclick = function(){insertAtCarret('"+ 153 jsEscape(edid)+"','"+ 154 jsEscape(tb[i]['insert'])+ 155 "');return false;}"); 156 toolbar.appendChild(btn); 157 break; 158 case 'signature': 159 if(typeof(SIG) != 'undefined' && SIG != ''){ 160 eval("btn.onclick = function(){insertAtCarret('"+ 161 jsEscape(edid)+"','"+ 162 jsEscape(SIG)+ 163 "');return false;}"); 164 toolbar.appendChild(btn); 165 } 166 break; 167 case 'picker': 168 createPicker('picker'+i, 169 tb[i]['list'], 170 tb[i]['icobase'], 171 edid); 172 eval("btn.onclick = function(){showPicker('picker"+i+ 173 "',this);return false;}"); 174 toolbar.appendChild(btn); 175 break; 176 case 'mediapopup': 177 eval("btn.onclick = function(){window.open('"+ 178 jsEscape(tb[i]['url']+NS)+"','"+ 179 jsEscape(tb[i]['name'])+"','"+ 180 jsEscape(tb[i]['options'])+ 181 "');return false;}"); 182 toolbar.appendChild(btn); 183 break; 184 } // end switch 185 } // end for 186} 187 188/** 189 * Format selection 190 * 191 * Apply tagOpen/tagClose to selection in textarea, use sampleText instead 192 * of selection if there is none. Copied and adapted from phpBB 193 * 194 * @author phpBB development team 195 * @author MediaWiki development team 196 * @author Andreas Gohr <andi@splitbrain.org> 197 * @author Jim Raynor <jim_raynor@web.de> 198 */ 199function insertTags(edid,tagOpen, tagClose, sampleText) { 200 var txtarea = document.getElementById(edid); 201 // IE 202 if(document.selection && !is_gecko) { 203 var theSelection = document.selection.createRange().text; 204 var replaced = true; 205 if(!theSelection){ 206 replaced = false; 207 theSelection=sampleText; 208 } 209 txtarea.focus(); 210 211 // This has change 212 var text = theSelection; 213 if(theSelection.charAt(theSelection.length - 1) == " "){// exclude ending space char, if any 214 theSelection = theSelection.substring(0, theSelection.length - 1); 215 r = document.selection.createRange(); 216 r.text = tagOpen + theSelection + tagClose + " "; 217 } else { 218 r = document.selection.createRange(); 219 r.text = tagOpen + theSelection + tagClose; 220 } 221 if(!replaced){ 222 r.moveStart('character',-text.length-tagClose.length); 223 r.moveEnd('character',-tagClose.length); 224 } 225 r.select(); 226 // Mozilla 227 } else if(txtarea.selectionStart || txtarea.selectionStart == '0') { 228 replaced = false; 229 var startPos = txtarea.selectionStart; 230 var endPos = txtarea.selectionEnd; 231 if(endPos - startPos){ replaced = true; } 232 var scrollTop=txtarea.scrollTop; 233 var myText = (txtarea.value).substring(startPos, endPos); 234 if(!myText) { myText=sampleText;} 235 if(myText.charAt(myText.length - 1) == " "){ // exclude ending space char, if any 236 subst = tagOpen + myText.substring(0, (myText.length - 1)) + tagClose + " "; 237 } else { 238 subst = tagOpen + myText + tagClose; 239 } 240 txtarea.value = txtarea.value.substring(0, startPos) + subst + 241 txtarea.value.substring(endPos, txtarea.value.length); 242 txtarea.focus(); 243 244 //set new selection 245 if(replaced){ 246 var cPos=startPos+(tagOpen.length+myText.length+tagClose.length); 247 txtarea.selectionStart=cPos; 248 txtarea.selectionEnd=cPos; 249 }else{ 250 txtarea.selectionStart=startPos+tagOpen.length; 251 txtarea.selectionEnd=startPos+tagOpen.length+myText.length; 252 } 253 txtarea.scrollTop=scrollTop; 254 // All others 255 } else { 256 var copy_alertText=alertText; 257 var re1=new RegExp("\\$1","g"); 258 var re2=new RegExp("\\$2","g"); 259 copy_alertText=copy_alertText.replace(re1,sampleText); 260 copy_alertText=copy_alertText.replace(re2,tagOpen+sampleText+tagClose); 261 262 if (sampleText) { 263 text=prompt(copy_alertText); 264 } else { 265 text=""; 266 } 267 if(!text) { text=sampleText;} 268 text=tagOpen+text+tagClose; 269 //append to the end 270 txtarea.value += "\n"+text; 271 272 // in Safari this causes scrolling 273 if(!is_safari) { 274 txtarea.focus(); 275 } 276 277 } 278 // reposition cursor if possible 279 if (txtarea.createTextRange){ 280 txtarea.caretPos = document.selection.createRange().duplicate(); 281 } 282} 283 284/* 285 * Insert the given value at the current cursor position 286 * 287 * @see http://www.alexking.org/index.php?content=software/javascript/content.php 288 */ 289function insertAtCarret(edid,value){ 290 var field = document.getElementById(edid); 291 292 //IE support 293 if (document.selection) { 294 field.focus(); 295 sel = document.selection.createRange(); 296 sel.text = value; 297 //MOZILLA/NETSCAPE support 298 }else if (field.selectionStart || field.selectionStart == '0') { 299 var startPos = field.selectionStart; 300 var endPos = field.selectionEnd; 301 var scrollTop = field.scrollTop; 302 field.value = field.value.substring(0, startPos) + 303 value + 304 field.value.substring(endPos, field.value.length); 305 306 field.focus(); 307 var cPos=startPos+(value.length); 308 field.selectionStart=cPos; 309 field.selectionEnd=cPos; 310 field.scrollTop=scrollTop; 311 } else { 312 field.value += "\n"+value; 313 } 314 // reposition cursor if possible 315 if (field.createTextRange){ 316 field.caretPos = document.selection.createRange().duplicate(); 317 } 318} 319 320 321/** 322 * global var used for not saved yet warning 323 */ 324var textChanged = false; 325 326/** 327 * Check for changes before leaving the page 328 */ 329function changeCheck(msg){ 330 if(textChanged){ 331 var ok = confirm(msg); 332 if(ok){ 333 // remove a possibly saved draft using ajax 334 var dwform = $('dw__editform'); 335 if(dwform){ 336 var params = 'call=draftdel'; 337 params += '&id='+dwform.elements.id.value; 338 params += '&user='+encodeURI(USERNAME); 339 340 var sackobj = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 341 sackobj.AjaxFailedAlert = ''; 342 sackobj.encodeURIString = false; 343 sackobj.runAJAX(params); 344 // we send this request blind without waiting for 345 // and handling the returned data 346 } 347 } 348 return ok; 349 }else{ 350 return true; 351 } 352} 353 354/** 355 * Add changeCheck to all Links and Forms (except those with a 356 * JSnocheck class), add handlers to monitor changes 357 * 358 * Sets focus to the editbox as well 359 */ 360function initChangeCheck(msg){ 361 if(!document.getElementById){ return false; } 362 // add change check for links 363 var links = document.getElementsByTagName('a'); 364 for(var i=0; i < links.length; i++){ 365 if(links[i].className.indexOf('JSnocheck') == -1){ 366 links[i].onclick = function(){return changeCheck(msg);}; 367 links[i].onkeypress = function(){return changeCheck(msg);}; 368 } 369 } 370 // add change check for forms 371 var forms = document.forms; 372 for(i=0; i < forms.length; i++){ 373 if(forms[i].className.indexOf('JSnocheck') == -1){ 374 forms[i].onsubmit = function(){return changeCheck(msg);}; 375 } 376 } 377 378 // reset change memory var on submit 379 var btn_save = document.getElementById('edbtn__save'); 380 btn_save.onclick = function(){ textChanged = false; }; 381 btn_save.onkeypress = function(){ textChanged = false; }; 382 var btn_prev = document.getElementById('edbtn__preview'); 383 btn_prev.onclick = function(){ textChanged = false; }; 384 btn_prev.onkeypress = function(){ textChanged = false; }; 385 386 // add change memory setter 387 var edit_text = document.getElementById('wiki__text'); 388 edit_text.onchange = function(){ 389 textChanged = true; //global var 390 summaryCheck(); 391 }; 392 edit_text.onkeyup = summaryCheck; 393 var summary = document.getElementById('edit__summary'); 394 summary.onchange = summaryCheck; 395 summary.onkeyup = summaryCheck; 396 397 // set focus 398 edit_text.focus(); 399} 400 401/** 402 * Checks if a summary was entered - if not the style is changed 403 * 404 * @author Andreas Gohr <andi@splitbrain.org> 405 */ 406function summaryCheck(){ 407 var sum = document.getElementById('edit__summary'); 408 if(sum.value === ''){ 409 sum.className='missing'; 410 }else{ 411 sum.className='edit'; 412 } 413} 414 415 416/** 417 * Class managing the timer to display a warning on a expiring lock 418 */ 419function locktimer_class(){ 420 this.sack = null; 421 this.timeout = 0; 422 this.timerID = null; 423 this.lasttime = null; 424 this.msg = ''; 425 this.pageid = ''; 426}; 427var locktimer = new locktimer_class(); 428 locktimer.init = function(timeout,msg,draft){ 429 // init values 430 locktimer.timeout = timeout*1000; 431 locktimer.msg = msg; 432 locktimer.draft = draft; 433 locktimer.lasttime = new Date(); 434 435 if(!$('dw__editform')) return; 436 locktimer.pageid = $('dw__editform').elements.id.value; 437 if(!locktimer.pageid) return; 438 439 // init ajax component 440 locktimer.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 441 locktimer.sack.AjaxFailedAlert = ''; 442 locktimer.sack.encodeURIString = false; 443 locktimer.sack.onCompletion = locktimer.refreshed; 444 445 // register refresh event 446 addEvent($('dw__editform').elements.wikitext,'keyup',function(){locktimer.refresh();}); 447 448 // start timer 449 locktimer.reset(); 450 }; 451 452 /** 453 * (Re)start the warning timer 454 */ 455 locktimer.reset = function(){ 456 locktimer.clear(); 457 locktimer.timerID = window.setTimeout("locktimer.warning()", locktimer.timeout); 458 }; 459 460 /** 461 * Display the warning about the expiring lock 462 */ 463 locktimer.warning = function(){ 464 locktimer.clear(); 465 alert(locktimer.msg); 466 }; 467 468 /** 469 * Remove the current warning timer 470 */ 471 locktimer.clear = function(){ 472 if(locktimer.timerID !== null){ 473 window.clearTimeout(locktimer.timerID); 474 locktimer.timerID = null; 475 } 476 }; 477 478 /** 479 * Refresh the lock via AJAX 480 * 481 * Called on keypresses in the edit area 482 */ 483 locktimer.refresh = function(){ 484 var now = new Date(); 485 // refresh every minute only 486 if(now.getTime() - locktimer.lasttime.getTime() > 30*1000){ //FIXME decide on time 487 var params = 'call=lock&id='+encodeURI(locktimer.pageid); 488 if(locktimer.draft){ 489 var dwform = $('dw__editform'); 490 params += '&prefix='+encodeURI(dwform.elements.prefix.value); 491 params += '&wikitext='+encodeURI(dwform.elements.wikitext.value); 492 params += '&suffix='+encodeURI(dwform.elements.suffix.value); 493 params += '&date='+encodeURI(dwform.elements.date.value); 494 } 495 locktimer.sack.runAJAX(params); 496 locktimer.lasttime = now; 497 } 498 }; 499 500 501 /** 502 * Callback. Resets the warning timer 503 */ 504 locktimer.refreshed = function(){ 505 var data = this.response; 506 var error = data.charAt(0); 507 data = data.substring(1); 508 509 $('draft__status').innerHTML=data; 510 if(error != '1') return; // locking failed 511 locktimer.reset(); 512 }; 513// end of locktimer class functions 514 515