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 += ' ['+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 if(icon.substr(0,1) == '/'){ 35 ico.src = icon; 36 }else{ 37 ico.src = DOKU_BASE+'lib/images/toolbar/'+icon; 38 } 39 btn.appendChild(ico); 40 41 return btn; 42} 43 44/** 45 * Creates a picker window for inserting text 46 * 47 * The given list can be an associative array with text,icon pairs 48 * or a simple list of text. Style the picker window through the picker 49 * class or the picker buttons with the pickerbutton class. Picker 50 * windows are appended to the body and created invisible. 51 * 52 * @author Andreas Gohr <andi@splitbrain.org> 53 */ 54function createPicker(id,list,icobase,edid){ 55 var cnt = list.length; 56 57 var picker = document.createElement('div'); 58 picker.className = 'picker'; 59 picker.id = id; 60 picker.style.position = 'absolute'; 61 picker.style.display = 'none'; 62 63 for(var key in list){ 64 if (!list.hasOwnProperty(key)) continue; 65 var btn = document.createElement('button'); 66 67 btn.className = 'pickerbutton'; 68 69 // associative array? 70 if(isNaN(key)){ 71 var ico = document.createElement('img'); 72 if(list[key].substr(0,1) == '/'){ 73 ico.src = list[key]; 74 }else{ 75 ico.src = DOKU_BASE+'lib/images/'+icobase+'/'+list[key]; 76 } 77 btn.title = key; 78 btn.appendChild(ico); 79 eval("btn.onclick = function(){pickerInsert('"+id+"','"+ 80 jsEscape(key)+"','"+ 81 jsEscape(edid)+"');return false;}"); 82 }else{ 83 var txt = document.createTextNode(list[key]); 84 btn.title = list[key]; 85 btn.appendChild(txt); 86 eval("btn.onclick = function(){pickerInsert('"+id+"','"+ 87 jsEscape(list[key])+"','"+ 88 jsEscape(edid)+"');return false;}"); 89 } 90 91 picker.appendChild(btn); 92 } 93 var body = document.getElementsByTagName('body')[0]; 94 body.appendChild(picker); 95} 96 97/** 98 * Called by picker buttons to insert Text and close the picker again 99 * 100 * @author Andreas Gohr <andi@splitbrain.org> 101 */ 102function pickerInsert(pickerid,text,edid){ 103 // insert 104 insertAtCarret(edid,text); 105 // close picker 106 pobj = document.getElementById(pickerid); 107 pobj.style.display = 'none'; 108} 109 110/** 111 * Show a previosly created picker window 112 * 113 * @author Andreas Gohr <andi@splitbrain.org> 114 */ 115function showPicker(pickerid,btn){ 116 var picker = document.getElementById(pickerid); 117 var x = findPosX(btn); 118 var y = findPosY(btn); 119 if(picker.style.display == 'none'){ 120 picker.style.display = 'block'; 121 picker.style.left = (x+3)+'px'; 122 picker.style.top = (y+btn.offsetHeight+3)+'px'; 123 }else{ 124 picker.style.display = 'none'; 125 } 126} 127 128/** 129 * Create a toolbar 130 * 131 * @param string tbid ID of the element where to insert the toolbar 132 * @param string edid ID of the editor textarea 133 * @param array tb Associative array defining the buttons 134 * @author Andreas Gohr <andi@splitbrain.org> 135 */ 136function initToolbar(tbid,edid,tb){ 137 var toolbar = $(tbid); 138 if(!toolbar) return; 139 140 //empty the toolbar area: 141 toolbar.innerHTML=''; 142 143 var cnt = tb.length; 144 for(var i=0; i<cnt; i++){ 145 // create new button 146 var btn = createToolButton(tb[i]['icon'], 147 tb[i]['title'], 148 tb[i]['key']); 149 150 var actionFunc = 'addBtnAction'+tb[i]['type'].charAt(0).toUpperCase()+tb[i]['type'].substring(1); 151 var exists = eval("typeof("+actionFunc+") == 'function'"); 152 if(exists) 153 { 154 if(eval(actionFunc+"(btn, tb[i], edid, i)")) 155 toolbar.appendChild(btn); 156 }else{ 157 alert('unknown type: '+tb[i]['type']); 158 } 159 } // end for 160} 161 162/** 163 * Add button action for format buttons 164 * 165 * @param DOMElement btn Button element to add the action to 166 * @param array props Associative array of button properties 167 * @param string edid ID of the editor textarea 168 * @return boolean If button should be appended 169 * @author Gabriel Birke <birke@d-scribe.de> 170 */ 171function addBtnActionFormat(btn, props, edid) 172{ 173 var sample = props['title']; 174 if(props['sample']){ sample = props['sample']; } 175 eval("btn.onclick = function(){insertTags('"+ 176 jsEscape(edid)+"','"+ 177 jsEscape(props['open'])+"','"+ 178 jsEscape(props['close'])+"','"+ 179 jsEscape(sample)+ 180 "');return false;}"); 181 182 return true; 183} 184 185/** 186 * Add button action for insert buttons 187 * 188 * @param DOMElement btn Button element to add the action to 189 * @param array props Associative array of button properties 190 * @param string edid ID of the editor textarea 191 * @return boolean If button should be appended 192 * @author Gabriel Birke <birke@d-scribe.de> 193 */ 194function addBtnActionInsert(btn, props, edid) 195{ 196 eval("btn.onclick = function(){insertAtCarret('"+ 197 jsEscape(edid)+"','"+ 198 jsEscape(props['insert'])+ 199 "');return false;}"); 200 return true; 201} 202 203/** 204 * Add button action for signature button 205 * 206 * @param DOMElement btn Button element to add the action to 207 * @param array props Associative array of button properties 208 * @param string edid ID of the editor textarea 209 * @return boolean If button should be appended 210 * @author Gabriel Birke <birke@d-scribe.de> 211 */ 212function addBtnActionSignature(btn, props, edid) 213{ 214 if(typeof(SIG) != 'undefined' && SIG != ''){ 215 eval("btn.onclick = function(){insertAtCarret('"+ 216 jsEscape(edid)+"','"+ 217 jsEscape(SIG)+ 218 "');return false;}"); 219 return true; 220 } 221 return false; 222} 223 224/** 225 * Add button action for picker buttons and create picker element 226 * 227 * @param DOMElement btn Button element to add the action to 228 * @param array props Associative array of button properties 229 * @param string edid ID of the editor textarea 230 * @param int id Unique number of the picker 231 * @return boolean If button should be appended 232 * @author Gabriel Birke <birke@d-scribe.de> 233 */ 234function addBtnActionPicker(btn, props, edid, id) 235{ 236 createPicker('picker'+id, 237 props['list'], 238 props['icobase'], 239 edid); 240 eval("btn.onclick = function(){showPicker('picker"+id+ 241 "',this);return false;}"); 242 return true; 243} 244 245/** 246 * Add button action for the mediapopup button 247 * 248 * @param DOMElement btn Button element to add the action to 249 * @param array props Associative array of button properties 250 * @return boolean If button should be appended 251 * @author Gabriel Birke <birke@d-scribe.de> 252 */ 253function addBtnActionMediapopup(btn, props) 254{ 255 eval("btn.onclick = function(){window.open('"+DOKU_BASE+ 256 jsEscape(props['url']+encodeURIComponent(NS))+"','"+ 257 jsEscape(props['name'])+"','"+ 258 jsEscape(props['options'])+ 259 "');return false;}"); 260 return true; 261} 262 263function addBtnActionAutohead(btn, props, edid, id) 264{ 265 eval("btn.onclick = function(){"+ 266 "insertHeadline('"+edid+"',"+props['mod']+",'"+jsEscape(props['text'])+"'); "+ 267 "return false};"); 268 return true; 269} 270 271 272 273 274/** 275 * Determine the current section level while editing 276 * 277 * @author Andreas Gohr <gohr@cosmocode.de> 278 */ 279function currentHeadlineLevel(textboxId){ 280 var field = $(textboxId); 281 var selection = getSelection(field); 282 var search = field.value.substr(0,selection.start); 283 var lasthl = search.lastIndexOf("\n=="); 284 if(lasthl == -1 && field.form.prefix){ 285 // we need to look in prefix context 286 search = field.form.prefix.value; 287 lasthl = search.lastIndexOf("\n=="); 288 } 289 search = search.substr(lasthl+1,6); 290 291 if(search == '======') return 1; 292 if(search.substr(0,5) == '=====') return 2; 293 if(search.substr(0,4) == '====') return 3; 294 if(search.substr(0,3) == '===') return 4; 295 if(search.substr(0,2) == '==') return 5; 296 297 return 0; 298} 299 300/** 301 * Insert a new headline based on the current section level 302 * 303 * @param string textboxId - the edit field ID 304 * @param int mod - the headline modificator ( -1, 0, 1) 305 * @param string text - the sample text passed to insertTags 306 */ 307function insertHeadline(textboxId,mod,text){ 308 var lvl = currentHeadlineLevel(textboxId); 309 310 311 // determine new level 312 lvl += mod; 313 if(lvl < 1) lvl = 1; 314 if(lvl > 5) lvl = 5; 315 316 var tags = '='; 317 for(var i=0; i<=5-lvl; i++) tags += '='; 318 insertTags(textboxId, tags+' ', ' '+tags+"\n", text); 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='+encodeURIComponent(dwform.elements.id.value); 338 339 var sackobj = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 340 sackobj.AjaxFailedAlert = ''; 341 sackobj.encodeURIString = false; 342 sackobj.runAJAX(params); 343 // we send this request blind without waiting for 344 // and handling the returned data 345 } 346 } 347 return ok; 348 }else{ 349 return true; 350 } 351} 352 353/** 354 * Add changeCheck to all Links and Forms (except those with a 355 * JSnocheck class), add handlers to monitor changes 356 * 357 * Sets focus to the editbox as well 358 */ 359function initChangeCheck(msg){ 360 if(!document.getElementById){ return false; } 361 // add change check for links 362 var links = document.getElementsByTagName('a'); 363 for(var i=0; i < links.length; i++){ 364 if(links[i].className.indexOf('JSnocheck') == -1){ 365 links[i].onclick = function(){ 366 var rc = changeCheck(msg); 367 if(window.event) window.event.returnValue = rc; 368 return rc; 369 }; 370 } 371 } 372 // add change check for forms 373 var forms = document.forms; 374 for(i=0; i < forms.length; i++){ 375 if(forms[i].className.indexOf('JSnocheck') == -1){ 376 forms[i].onsubmit = function(){ 377 var rc = changeCheck(msg); 378 if(window.event) window.event.returnValue = rc; 379 return rc; 380 }; 381 } 382 } 383 384 // reset change memory var on submit 385 var btn_save = document.getElementById('edbtn__save'); 386 btn_save.onclick = function(){ textChanged = false; }; 387 var btn_prev = document.getElementById('edbtn__preview'); 388 btn_prev.onclick = function(){ textChanged = false; }; 389 390 // add change memory setter 391 var edit_text = document.getElementById('wiki__text'); 392 edit_text.onchange = function(){ 393 textChanged = true; //global var 394 summaryCheck(); 395 }; 396 edit_text.onkeyup = summaryCheck; 397 var summary = document.getElementById('edit__summary'); 398 addEvent(summary, 'change', summaryCheck); 399 addEvent(summary, 'keyup', summaryCheck); 400 if (textChanged) summaryCheck(); 401 402 // set focus 403 edit_text.focus(); 404} 405 406/** 407 * Checks if a summary was entered - if not the style is changed 408 * 409 * @author Andreas Gohr <andi@splitbrain.org> 410 */ 411function summaryCheck(){ 412 var sum = document.getElementById('edit__summary'); 413 if(sum.value === ''){ 414 sum.className='missing'; 415 }else{ 416 sum.className='edit'; 417 } 418} 419 420 421/** 422 * Class managing the timer to display a warning on a expiring lock 423 */ 424function locktimer_class(){ 425 this.sack = null; 426 this.timeout = 0; 427 this.timerID = null; 428 this.lasttime = null; 429 this.msg = ''; 430 this.pageid = ''; 431}; 432var locktimer = new locktimer_class(); 433 locktimer.init = function(timeout,msg,draft){ 434 // init values 435 locktimer.timeout = timeout*1000; 436 locktimer.msg = msg; 437 locktimer.draft = draft; 438 locktimer.lasttime = new Date(); 439 440 if(!$('dw__editform')) return; 441 locktimer.pageid = $('dw__editform').elements.id.value; 442 if(!locktimer.pageid) return; 443 444 // init ajax component 445 locktimer.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 446 locktimer.sack.AjaxFailedAlert = ''; 447 locktimer.sack.encodeURIString = false; 448 locktimer.sack.onCompletion = locktimer.refreshed; 449 450 // register refresh event 451 addEvent($('dw__editform').elements.wikitext,'keypress',function(){locktimer.refresh();}); 452 453 // start timer 454 locktimer.reset(); 455 }; 456 457 /** 458 * (Re)start the warning timer 459 */ 460 locktimer.reset = function(){ 461 locktimer.clear(); 462 locktimer.timerID = window.setTimeout("locktimer.warning()", locktimer.timeout); 463 }; 464 465 /** 466 * Display the warning about the expiring lock 467 */ 468 locktimer.warning = function(){ 469 locktimer.clear(); 470 alert(locktimer.msg); 471 }; 472 473 /** 474 * Remove the current warning timer 475 */ 476 locktimer.clear = function(){ 477 if(locktimer.timerID !== null){ 478 window.clearTimeout(locktimer.timerID); 479 locktimer.timerID = null; 480 } 481 }; 482 483 /** 484 * Refresh the lock via AJAX 485 * 486 * Called on keypresses in the edit area 487 */ 488 locktimer.refresh = function(){ 489 var now = new Date(); 490 // refresh every minute only 491 if(now.getTime() - locktimer.lasttime.getTime() > 30*1000){ //FIXME decide on time 492 var params = 'call=lock&id='+encodeURIComponent(locktimer.pageid); 493 if(locktimer.draft){ 494 var dwform = $('dw__editform'); 495 params += '&prefix='+encodeURIComponent(dwform.elements.prefix.value); 496 params += '&wikitext='+encodeURIComponent(dwform.elements.wikitext.value); 497 params += '&suffix='+encodeURIComponent(dwform.elements.suffix.value); 498 params += '&date='+encodeURIComponent(dwform.elements.date.value); 499 } 500 locktimer.sack.runAJAX(params); 501 locktimer.lasttime = now; 502 } 503 }; 504 505 506 /** 507 * Callback. Resets the warning timer 508 */ 509 locktimer.refreshed = function(){ 510 var data = this.response; 511 var error = data.charAt(0); 512 data = data.substring(1); 513 514 $('draft__status').innerHTML=data; 515 if(error != '1') return; // locking failed 516 locktimer.reset(); 517 }; 518// end of locktimer class functions 519 520