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 * Add button action for signature button 130 * 131 * @param DOMElement btn Button element to add the action to 132 * @param array props Associative array of button properties 133 * @param string edid ID of the editor textarea 134 * @return boolean If button should be appended 135 * @author Gabriel Birke <birke@d-scribe.de> 136 */ 137function addBtnActionSignature(btn, props, edid) 138{ 139 if(typeof(SIG) != 'undefined' && SIG != ''){ 140 eval("btn.onclick = function(){insertAtCarret('"+ 141 jsEscape(edid)+"','"+ 142 jsEscape(SIG)+ 143 "');return false;}"); 144 return true; 145 } 146 return false; 147} 148 149/** 150 * Add button action for picker buttons and create picker element 151 * 152 * @param DOMElement btn Button element to add the action to 153 * @param array props Associative array of button properties 154 * @param string edid ID of the editor textarea 155 * @param int id Unique number of the picker 156 * @return boolean If button should be appended 157 * @author Gabriel Birke <birke@d-scribe.de> 158 */ 159function addBtnActionPicker(btn, props, edid, id) 160{ 161 createPicker('picker'+id, 162 props['list'], 163 props['icobase'], 164 edid); 165 eval("btn.onclick = function(){showPicker('picker"+id+ 166 "',this);return false;}"); 167 return true; 168} 169 170/** 171 * Add button action for the mediapopup button 172 * 173 * @param DOMElement btn Button element to add the action to 174 * @param array props Associative array of button properties 175 * @return boolean If button should be appended 176 * @author Gabriel Birke <birke@d-scribe.de> 177 */ 178function addBtnActionMediapopup(btn, props) 179{ 180 eval("btn.onclick = function(){window.open('"+DOKU_BASE+ 181 jsEscape(props['url']+encodeURIComponent(NS))+"','"+ 182 jsEscape(props['name'])+"','"+ 183 jsEscape(props['options'])+ 184 "');return false;}"); 185 return true; 186} 187 188function addBtnActionAutohead(btn, props, edid, id) 189{ 190 eval("btn.onclick = function(){"+ 191 "insertHeadline('"+edid+"',"+props['mod']+",'"+jsEscape(props['text'])+"'); "+ 192 "return false};"); 193 return true; 194} 195 196 197 198 199/** 200 * Determine the current section level while editing 201 * 202 * @author Andreas Gohr <gohr@cosmocode.de> 203 */ 204function currentHeadlineLevel(textboxId){ 205 var field = $(textboxId); 206 var selection = getSelection(field); 207 var search = field.value.substr(0,selection.start); 208 var lasthl = search.lastIndexOf("\n=="); 209 if(lasthl == -1 && field.form.prefix){ 210 // we need to look in prefix context 211 search = field.form.prefix.value; 212 lasthl = search.lastIndexOf("\n=="); 213 } 214 search = search.substr(lasthl+1,6); 215 216 if(search == '======') return 1; 217 if(search.substr(0,5) == '=====') return 2; 218 if(search.substr(0,4) == '====') return 3; 219 if(search.substr(0,3) == '===') return 4; 220 if(search.substr(0,2) == '==') return 5; 221 222 return 0; 223} 224 225/** 226 * Insert a new headline based on the current section level 227 * 228 * @param string textboxId - the edit field ID 229 * @param int mod - the headline modificator ( -1, 0, 1) 230 * @param string text - the sample text passed to insertTags 231 */ 232function insertHeadline(textboxId,mod,text){ 233 var lvl = currentHeadlineLevel(textboxId); 234 235 236 // determine new level 237 lvl += mod; 238 if(lvl < 1) lvl = 1; 239 if(lvl > 5) lvl = 5; 240 241 var tags = '='; 242 for(var i=0; i<=5-lvl; i++) tags += '='; 243 insertTags(textboxId, tags+' ', ' '+tags+"\n", text); 244} 245 246/** 247 * global var used for not saved yet warning 248 */ 249var textChanged = false; 250 251/** 252 * Check for changes before leaving the page 253 */ 254function changeCheck(msg){ 255 if(textChanged){ 256 var ok = confirm(msg); 257 if(ok){ 258 // remove a possibly saved draft using ajax 259 var dwform = $('dw__editform'); 260 if(dwform){ 261 var params = 'call=draftdel'; 262 params += '&id='+encodeURIComponent(dwform.elements.id.value); 263 264 var sackobj = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 265 sackobj.AjaxFailedAlert = ''; 266 sackobj.encodeURIString = false; 267 sackobj.runAJAX(params); 268 // we send this request blind without waiting for 269 // and handling the returned data 270 } 271 } 272 return ok; 273 }else{ 274 return true; 275 } 276} 277 278/** 279 * Add changeCheck to all Links and Forms (except those with a 280 * JSnocheck class), add handlers to monitor changes 281 * 282 * Sets focus to the editbox as well 283 */ 284function initChangeCheck(msg){ 285 if(!document.getElementById){ return false; } 286 // add change check for links 287 var links = document.getElementsByTagName('a'); 288 for(var i=0; i < links.length; i++){ 289 if(links[i].className.indexOf('JSnocheck') == -1){ 290 links[i].onclick = function(){ 291 var rc = changeCheck(msg); 292 if(window.event) window.event.returnValue = rc; 293 return rc; 294 }; 295 } 296 } 297 // add change check for forms 298 var forms = document.forms; 299 for(i=0; i < forms.length; i++){ 300 if(forms[i].className.indexOf('JSnocheck') == -1){ 301 forms[i].onsubmit = function(){ 302 var rc = changeCheck(msg); 303 if(window.event) window.event.returnValue = rc; 304 return rc; 305 }; 306 } 307 } 308 309 // reset change memory var on submit 310 var btn_save = document.getElementById('edbtn__save'); 311 btn_save.onclick = function(){ textChanged = false; }; 312 var btn_prev = document.getElementById('edbtn__preview'); 313 btn_prev.onclick = function(){ textChanged = false; }; 314 315 // add change memory setter 316 var edit_text = document.getElementById('wiki__text'); 317 edit_text.onchange = function(){ 318 textChanged = true; //global var 319 summaryCheck(); 320 }; 321 edit_text.onkeyup = summaryCheck; 322 var summary = document.getElementById('edit__summary'); 323 addEvent(summary, 'change', summaryCheck); 324 addEvent(summary, 'keyup', summaryCheck); 325 if (textChanged) summaryCheck(); 326 327 // set focus 328 edit_text.focus(); 329} 330 331/** 332 * Checks if a summary was entered - if not the style is changed 333 * 334 * @author Andreas Gohr <andi@splitbrain.org> 335 */ 336function summaryCheck(){ 337 var sum = document.getElementById('edit__summary'); 338 if(sum.value === ''){ 339 sum.className='missing'; 340 }else{ 341 sum.className='edit'; 342 } 343} 344 345 346/** 347 * Class managing the timer to display a warning on a expiring lock 348 */ 349function locktimer_class(){ 350 this.sack = null; 351 this.timeout = 0; 352 this.timerID = null; 353 this.lasttime = null; 354 this.msg = ''; 355 this.pageid = ''; 356}; 357var locktimer = new locktimer_class(); 358 locktimer.init = function(timeout,msg,draft){ 359 // init values 360 locktimer.timeout = timeout*1000; 361 locktimer.msg = msg; 362 locktimer.draft = draft; 363 locktimer.lasttime = new Date(); 364 365 if(!$('dw__editform')) return; 366 locktimer.pageid = $('dw__editform').elements.id.value; 367 if(!locktimer.pageid) return; 368 369 // init ajax component 370 locktimer.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 371 locktimer.sack.AjaxFailedAlert = ''; 372 locktimer.sack.encodeURIString = false; 373 locktimer.sack.onCompletion = locktimer.refreshed; 374 375 // register refresh event 376 addEvent($('dw__editform').elements.wikitext,'keypress',function(){locktimer.refresh();}); 377 378 // start timer 379 locktimer.reset(); 380 }; 381 382 /** 383 * (Re)start the warning timer 384 */ 385 locktimer.reset = function(){ 386 locktimer.clear(); 387 locktimer.timerID = window.setTimeout("locktimer.warning()", locktimer.timeout); 388 }; 389 390 /** 391 * Display the warning about the expiring lock 392 */ 393 locktimer.warning = function(){ 394 locktimer.clear(); 395 alert(locktimer.msg); 396 }; 397 398 /** 399 * Remove the current warning timer 400 */ 401 locktimer.clear = function(){ 402 if(locktimer.timerID !== null){ 403 window.clearTimeout(locktimer.timerID); 404 locktimer.timerID = null; 405 } 406 }; 407 408 /** 409 * Refresh the lock via AJAX 410 * 411 * Called on keypresses in the edit area 412 */ 413 locktimer.refresh = function(){ 414 var now = new Date(); 415 // refresh every minute only 416 if(now.getTime() - locktimer.lasttime.getTime() > 30*1000){ //FIXME decide on time 417 var params = 'call=lock&id='+encodeURIComponent(locktimer.pageid); 418 if(locktimer.draft){ 419 var dwform = $('dw__editform'); 420 params += '&prefix='+encodeURIComponent(dwform.elements.prefix.value); 421 params += '&wikitext='+encodeURIComponent(dwform.elements.wikitext.value); 422 params += '&suffix='+encodeURIComponent(dwform.elements.suffix.value); 423 params += '&date='+encodeURIComponent(dwform.elements.date.value); 424 } 425 locktimer.sack.runAJAX(params); 426 locktimer.lasttime = now; 427 } 428 }; 429 430 431 /** 432 * Callback. Resets the warning timer 433 */ 434 locktimer.refreshed = function(){ 435 var data = this.response; 436 var error = data.charAt(0); 437 data = data.substring(1); 438 439 $('draft__status').innerHTML=data; 440 if(error != '1') return; // locking failed 441 locktimer.reset(); 442 }; 443// end of locktimer class functions 444 445