1/** 2 * Functions for text editing (toolbar stuff) 3 * 4 * @todo most of the stuff in here should be revamped and then moved to toolbar.js 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,classname){ 16 var btn = document.createElement('button'); 17 var ico = document.createElement('img'); 18 19 // preapare the basic button stuff 20 btn.className = 'toolbutton'; 21 if(classname){ 22 btn.className += ' '+classname; 23 } 24 btn.title = label; 25 if(key){ 26 btn.title += ' ['+key.toUpperCase()+']'; 27 btn.accessKey = key; 28 } 29 30 // set IDs if given 31 if(id){ 32 btn.id = id; 33 ico.id = id+'_ico'; 34 } 35 36 // create the icon and add it to the button 37 if(icon.substr(0,1) == '/'){ 38 ico.src = icon; 39 }else{ 40 ico.src = DOKU_BASE+'lib/images/toolbar/'+icon; 41 } 42 btn.appendChild(ico); 43 44 return btn; 45} 46 47/** 48 * Creates a picker window for inserting text 49 * 50 * The given list can be an associative array with text,icon pairs 51 * or a simple list of text. Style the picker window through the picker 52 * class or the picker buttons with the pickerbutton class. Picker 53 * windows are appended to the body and created invisible. 54 * 55 * @param string id the ID to assign to the picker 56 * @param array props the properties for the picker 57 * @param string edid the ID of the textarea 58 * @rteurn DOMobject the created picker 59 * @author Andreas Gohr <andi@splitbrain.org> 60 */ 61function createPicker(id,props,edid){ 62 var icobase = props['icobase']; 63 var list = props['list']; 64 65 // create the wrapping div 66 var picker = document.createElement('div'); 67 picker.className = 'picker'; 68 if(props['class']){ 69 picker.className += ' '+props['class']; 70 } 71 picker.id = id; 72 picker.style.position = 'absolute'; 73 picker.style.marginLeft = '-10000px'; // no display:none, to keep access keys working 74 picker.style.marginTop = '-10000px'; 75 76 for(var key in list){ 77 if (!list.hasOwnProperty(key)) continue; 78 79 if(isNaN(key)){ 80 // associative array -> treat as image/value pairs 81 var btn = document.createElement('button'); 82 btn.className = 'pickerbutton'; 83 var ico = document.createElement('img'); 84 if(list[key].substr(0,1) == '/'){ 85 ico.src = list[key]; 86 }else{ 87 ico.src = DOKU_BASE+'lib/images/'+icobase+'/'+list[key]; 88 } 89 btn.title = key; 90 btn.appendChild(ico); 91 addEvent(btn,'click',bind(pickerInsert,key,edid)); 92 picker.appendChild(btn); 93 }else if(isString(list[key])){ 94 // a list of text -> treat as text picker 95 var btn = document.createElement('button'); 96 btn.className = 'pickerbutton'; 97 var txt = document.createTextNode(list[key]); 98 btn.title = list[key]; 99 btn.appendChild(txt); 100 addEvent(btn,'click',bind(pickerInsert,list[key],edid)); 101 picker.appendChild(btn); 102 }else{ 103 // a list of lists -> treat it as subtoolbar 104 initToolbar(picker,edid,list); 105 break; // all buttons handled already 106 } 107 108 } 109 var body = document.getElementsByTagName('body')[0]; 110 body.appendChild(picker); 111 return picker; 112} 113 114/** 115 * Called by picker buttons to insert Text and close the picker again 116 * 117 * @author Andreas Gohr <andi@splitbrain.org> 118 */ 119function pickerInsert(text,edid){ 120 insertAtCarret(edid,text); 121 pickerClose(); 122} 123 124/** 125 * Add button action for signature button 126 * 127 * @param DOMElement btn Button element to add the action to 128 * @param array props Associative array of button properties 129 * @param string edid ID of the editor textarea 130 * @return boolean If button should be appended 131 * @author Gabriel Birke <birke@d-scribe.de> 132 */ 133function addBtnActionSignature(btn, props, edid) { 134 if(typeof(SIG) != 'undefined' && SIG != ''){ 135 addEvent(btn,'click',bind(insertAtCarret,edid,SIG)); 136 return true; 137 } 138 return false; 139} 140 141/** 142 * Make intended formattings easier to handle 143 * 144 * Listens to all key inputs and handle indentions 145 * of lists and code blocks 146 * 147 * Currently handles space, backspce and enter presses 148 * 149 * @author Andreas Gohr <andi@splitbrain.org> 150 * @fixme handle tabs 151 */ 152function keyHandler(e){ 153 if(e.keyCode != 13 && 154 e.keyCode != 8 && 155 e.keyCode != 32) return; 156 var field = e.target; 157 var selection = getSelection(field); 158 var search = "\n"+field.value.substr(0,selection.start); 159 var linestart = Math.max(search.lastIndexOf("\n"), 160 search.lastIndexOf("\r")); //IE workaround 161 search = search.substr(linestart); 162 163 164 if(e.keyCode == 13){ // Enter 165 // keep current indention for lists and code 166 var match = search.match(/(\n +([\*-] ?)?)/); 167 if(match){ 168 var scroll = field.scrollHeight; 169 insertAtCarret(field.id,match[1]); 170 field.scrollTop += (field.scrollHeight - scroll); 171 e.preventDefault(); // prevent enter key 172 return false; 173 } 174 }else if(e.keyCode == 8){ // Backspace 175 // unindent lists 176 var match = search.match(/(\n +)([*-] ?)$/); 177 if(match){ 178 var spaces = match[1].length-1; 179 180 if(spaces > 3){ // unindent one level 181 field.value = field.value.substr(0,linestart)+ 182 field.value.substr(linestart+2); 183 selection.start = selection.start - 2; 184 selection.end = selection.start; 185 }else{ // delete list point 186 field.value = field.value.substr(0,linestart)+ 187 field.value.substr(selection.start); 188 selection.start = linestart; 189 selection.end = linestart; 190 } 191 setSelection(selection); 192 e.preventDefault(); // prevent backspace 193 return false; 194 } 195 }else if(e.keyCode == 32){ // Space 196 // intend list item 197 var match = search.match(/(\n +)([*-] )$/); 198 if(match){ 199 field.value = field.value.substr(0,linestart)+' '+ 200 field.value.substr(linestart); 201 selection.start = selection.start + 2; 202 selection.end = selection.start; 203 setSelection(selection); 204 e.preventDefault(); // prevent space 205 return false; 206 } 207 } 208} 209 210//FIXME consolidate somewhere else 211addInitEvent(function(){ 212 var field = $('wiki__text'); 213 if(!field) return; 214 addEvent(field,'keydown',keyHandler); 215}); 216 217/** 218 * Determine the current section level while editing 219 * 220 * @author Andreas Gohr <gohr@cosmocode.de> 221 */ 222function currentHeadlineLevel(textboxId){ 223 var field = $(textboxId); 224 var selection = getSelection(field); 225 var search = "\n"+field.value.substr(0,selection.start); 226 var lasthl = search.lastIndexOf("\n=="); 227 if(lasthl == -1 && field.form.prefix){ 228 // we need to look in prefix context 229 search = field.form.prefix.value; 230 lasthl = search.lastIndexOf("\n=="); 231 } 232 search = search.substr(lasthl+1,6); 233 234 if(search == '======') return 1; 235 if(search.substr(0,5) == '=====') return 2; 236 if(search.substr(0,4) == '====') return 3; 237 if(search.substr(0,3) == '===') return 4; 238 if(search.substr(0,2) == '==') return 5; 239 240 return 0; 241} 242 243 244/** 245 * global var used for not saved yet warning 246 */ 247var textChanged = false; 248 249/** 250 * Check for changes before leaving the page 251 */ 252function changeCheck(msg){ 253 if(textChanged){ 254 var ok = confirm(msg); 255 if(ok){ 256 // remove a possibly saved draft using ajax 257 var dwform = $('dw__editform'); 258 if(dwform){ 259 var params = 'call=draftdel'; 260 params += '&id='+encodeURIComponent(dwform.elements.id.value); 261 262 var sackobj = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 263 sackobj.AjaxFailedAlert = ''; 264 sackobj.encodeURIString = false; 265 sackobj.runAJAX(params); 266 // we send this request blind without waiting for 267 // and handling the returned data 268 } 269 } 270 return ok; 271 }else{ 272 return true; 273 } 274} 275 276/** 277 * Add changeCheck to all Links and Forms (except those with a 278 * JSnocheck class), add handlers to monitor changes 279 * 280 * Sets focus to the editbox as well 281 * 282 * @fixme this is old and crappy code. needs to be redone 283 */ 284function initChangeCheck(msg){ 285 var edit_text = document.getElementById('wiki__text'); 286 if(!edit_text) return; 287 if(edit_text.readOnly) return; 288 if(!$('dw__editform')) return; 289 290 // add change check for links 291 var links = document.getElementsByTagName('a'); 292 for(var i=0; i < links.length; i++){ 293 if(links[i].className.indexOf('JSnocheck') == -1){ 294 links[i].onclick = function(){ 295 var rc = changeCheck(msg); 296 if(window.event) window.event.returnValue = rc; 297 return rc; 298 }; 299 } 300 } 301 // add change check for forms 302 var forms = document.forms; 303 for(i=0; i < forms.length; i++){ 304 if(forms[i].className.indexOf('JSnocheck') == -1){ 305 forms[i].onsubmit = function(){ 306 var rc = changeCheck(msg); 307 if(window.event) window.event.returnValue = rc; 308 return rc; 309 }; 310 } 311 } 312 313 // reset change memory var on submit 314 var btn_save = document.getElementById('edbtn__save'); 315 btn_save.onclick = function(){ textChanged = false; }; 316 var btn_prev = document.getElementById('edbtn__preview'); 317 btn_prev.onclick = function(){ textChanged = false; }; 318 319 // add change memory setter 320 edit_text.onchange = function(){ 321 textChanged = true; //global var 322 summaryCheck(); 323 }; 324 var summary = document.getElementById('edit__summary'); 325 addEvent(summary, 'change', summaryCheck); 326 addEvent(summary, 'keyup', summaryCheck); 327 if (textChanged) summaryCheck(); 328 329 // set focus 330 edit_text.focus(); 331} 332 333/** 334 * Checks if a summary was entered - if not the style is changed 335 * 336 * @author Andreas Gohr <andi@splitbrain.org> 337 */ 338function summaryCheck(){ 339 var sum = document.getElementById('edit__summary'); 340 if(sum.value === ''){ 341 sum.className='missing'; 342 }else{ 343 sum.className='edit'; 344 } 345} 346 347 348/** 349 * Class managing the timer to display a warning on a expiring lock 350 */ 351function locktimer_class(){ 352 this.sack = null; 353 this.timeout = 0; 354 this.timerID = null; 355 this.lasttime = null; 356 this.msg = ''; 357 this.pageid = ''; 358}; 359var locktimer = new locktimer_class(); 360 locktimer.init = function(timeout,msg,draft){ 361 // init values 362 locktimer.timeout = timeout*1000; 363 locktimer.msg = msg; 364 locktimer.draft = draft; 365 locktimer.lasttime = new Date(); 366 367 if(!$('dw__editform')) return; 368 locktimer.pageid = $('dw__editform').elements.id.value; 369 if(!locktimer.pageid) return; 370 371 // init ajax component 372 locktimer.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 373 locktimer.sack.AjaxFailedAlert = ''; 374 locktimer.sack.encodeURIString = false; 375 locktimer.sack.onCompletion = locktimer.refreshed; 376 377 // register refresh event 378 addEvent($('dw__editform').elements.wikitext,'keypress',function(){locktimer.refresh();}); 379 380 // start timer 381 locktimer.reset(); 382 }; 383 384 /** 385 * (Re)start the warning timer 386 */ 387 locktimer.reset = function(){ 388 locktimer.clear(); 389 locktimer.timerID = window.setTimeout("locktimer.warning()", locktimer.timeout); 390 }; 391 392 /** 393 * Display the warning about the expiring lock 394 */ 395 locktimer.warning = function(){ 396 locktimer.clear(); 397 alert(locktimer.msg); 398 }; 399 400 /** 401 * Remove the current warning timer 402 */ 403 locktimer.clear = function(){ 404 if(locktimer.timerID !== null){ 405 window.clearTimeout(locktimer.timerID); 406 locktimer.timerID = null; 407 } 408 }; 409 410 /** 411 * Refresh the lock via AJAX 412 * 413 * Called on keypresses in the edit area 414 */ 415 locktimer.refresh = function(){ 416 var now = new Date(); 417 // refresh every minute only 418 if(now.getTime() - locktimer.lasttime.getTime() > 30*1000){ //FIXME decide on time 419 var params = 'call=lock&id='+encodeURIComponent(locktimer.pageid); 420 if(locktimer.draft){ 421 var dwform = $('dw__editform'); 422 params += '&prefix='+encodeURIComponent(dwform.elements.prefix.value); 423 params += '&wikitext='+encodeURIComponent(dwform.elements.wikitext.value); 424 params += '&suffix='+encodeURIComponent(dwform.elements.suffix.value); 425 params += '&date='+encodeURIComponent(dwform.elements.date.value); 426 } 427 locktimer.sack.runAJAX(params); 428 locktimer.lasttime = now; 429 } 430 }; 431 432 433 /** 434 * Callback. Resets the warning timer 435 */ 436 locktimer.refreshed = function(){ 437 var data = this.response; 438 var error = data.charAt(0); 439 data = data.substring(1); 440 441 $('draft__status').innerHTML=data; 442 if(error != '1') return; // locking failed 443 locktimer.reset(); 444 }; 445// end of locktimer class functions 446 447