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