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){ 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'; 65 if(props['class']){ 66 picker.className += ' '+props['class']; 67 } 68 picker.id = id; 69 picker.style.position = 'absolute'; 70 picker.style.marginLeft = '-10000px'; // no display:none, to keep access keys working 71 picker.style.marginTop = '-10000px'; 72 73 for(var key in list){ 74 if (!list.hasOwnProperty(key)) continue; 75 76 if(isNaN(key)){ 77 // associative array -> treat as image/value pairs 78 var btn = document.createElement('button'); 79 btn.className = 'pickerbutton'; 80 var ico = document.createElement('img'); 81 if(list[key].substr(0,1) == '/'){ 82 ico.src = list[key]; 83 }else{ 84 ico.src = DOKU_BASE+'lib/images/'+icobase+'/'+list[key]; 85 } 86 btn.title = key; 87 btn.appendChild(ico); 88 addEvent(btn,'click',bind(pickerInsert,key,edid)); 89 picker.appendChild(btn); 90 }else if(isString(list[key])){ 91 // a list of text -> treat as text picker 92 var btn = document.createElement('button'); 93 btn.className = 'pickerbutton'; 94 var txt = document.createTextNode(list[key]); 95 btn.title = list[key]; 96 btn.appendChild(txt); 97 addEvent(btn,'click',bind(pickerInsert,list[key],edid)); 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 insertAtCarret(edid,text); 118 pickerClose(); 119} 120 121/** 122 * Add button action for signature button 123 * 124 * @param DOMElement btn Button element to add the action to 125 * @param array props Associative array of button properties 126 * @param string edid ID of the editor textarea 127 * @return boolean If button should be appended 128 * @author Gabriel Birke <birke@d-scribe.de> 129 */ 130function addBtnActionSignature(btn, props, edid) { 131 if(typeof(SIG) != 'undefined' && SIG != ''){ 132 addEvent(btn,'click',bind(insertAtCarret,edid,SIG)); 133 return true; 134 } 135 return false; 136} 137 138/** 139 * Make intended formattings easier to handle 140 * 141 * Listens to all key inputs and handle indentions 142 * of lists and code blocks 143 * 144 * Currently handles space, backspce and enter presses 145 * 146 * @author Andreas Gohr <andi@splitbrain.org> 147 * @fixme handle tabs 148 */ 149function keyHandler(e){ 150 if(e.keyCode != 13 && 151 e.keyCode != 8 && 152 e.keyCode != 32) return; 153 var field = e.target; 154 var selection = getSelection(field); 155 if(selection.getLength()) return; //there was text selected, keep standard behavior 156 var search = "\n"+field.value.substr(0,selection.start); 157 var linestart = Math.max(search.lastIndexOf("\n"), 158 search.lastIndexOf("\r")); //IE workaround 159 search = search.substr(linestart); 160 161 162 if(e.keyCode == 13){ // Enter 163 // keep current indention for lists and code 164 var match = search.match(/(\n +([\*-] ?)?)/); 165 if(match){ 166 var scroll = field.scrollHeight; 167 insertAtCarret(field.id,match[1]); 168 field.scrollTop += (field.scrollHeight - scroll); 169 e.preventDefault(); // prevent enter key 170 return false; 171 } 172 }else if(e.keyCode == 8){ // Backspace 173 // unindent lists 174 var match = search.match(/(\n +)([*-] ?)$/); 175 if(match){ 176 var spaces = match[1].length-1; 177 178 if(spaces > 3){ // unindent one level 179 field.value = field.value.substr(0,linestart)+ 180 field.value.substr(linestart+2); 181 selection.start = selection.start - 2; 182 selection.end = selection.start; 183 }else{ // delete list point 184 field.value = field.value.substr(0,linestart)+ 185 field.value.substr(selection.start); 186 selection.start = linestart; 187 selection.end = linestart; 188 } 189 setSelection(selection); 190 e.preventDefault(); // prevent backspace 191 return false; 192 } 193 }else if(e.keyCode == 32){ // Space 194 // intend list item 195 var match = search.match(/(\n +)([*-] )$/); 196 if(match){ 197 field.value = field.value.substr(0,linestart)+' '+ 198 field.value.substr(linestart); 199 selection.start = selection.start + 2; 200 selection.end = selection.start; 201 setSelection(selection); 202 e.preventDefault(); // prevent space 203 return false; 204 } 205 } 206} 207 208//FIXME consolidate somewhere else 209addInitEvent(function(){ 210 var field = $('wiki__text'); 211 if(!field) return; 212 addEvent(field,'keydown',keyHandler); 213}); 214 215/** 216 * Determine the current section level while editing 217 * 218 * @author Andreas Gohr <gohr@cosmocode.de> 219 */ 220function currentHeadlineLevel(textboxId){ 221 var field = $(textboxId); 222 var selection = getSelection(field); 223 var search = "\n"+field.value.substr(0,selection.start); 224 var lasthl = search.lastIndexOf("\n=="); 225 if(lasthl == -1 && field.form.prefix){ 226 // we need to look in prefix context 227 search = field.form.prefix.value; 228 lasthl = search.lastIndexOf("\n=="); 229 } 230 search = search.substr(lasthl+1,6); 231 232 if(search == '======') return 1; 233 if(search.substr(0,5) == '=====') return 2; 234 if(search.substr(0,4) == '====') return 3; 235 if(search.substr(0,3) == '===') return 4; 236 if(search.substr(0,2) == '==') return 5; 237 238 return 0; 239} 240 241 242/** 243 * global var used for not saved yet warning 244 */ 245var textChanged = false; 246 247/** 248 * Check for changes before leaving the page 249 */ 250function changeCheck(msg){ 251 if(textChanged){ 252 var ok = confirm(msg); 253 if(ok){ 254 // remove a possibly saved draft using ajax 255 var dwform = $('dw__editform'); 256 if(dwform){ 257 var params = 'call=draftdel'; 258 params += '&id='+encodeURIComponent(dwform.elements.id.value); 259 260 var sackobj = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 261 sackobj.AjaxFailedAlert = ''; 262 sackobj.encodeURIString = false; 263 sackobj.runAJAX(params); 264 // we send this request blind without waiting for 265 // and handling the returned data 266 } 267 } 268 return ok; 269 }else{ 270 return true; 271 } 272} 273 274/** 275 * Add changeCheck to all Links and Forms (except those with a 276 * JSnocheck class), add handlers to monitor changes 277 * 278 * Sets focus to the editbox as well 279 * 280 * @fixme this is old and crappy code. needs to be redone 281 */ 282function initChangeCheck(msg){ 283 var edit_text = document.getElementById('wiki__text'); 284 if(!edit_text) return; 285 if(edit_text.readOnly) return; 286 if(!$('dw__editform')) return; 287 288 // add change check for links 289 var links = document.getElementsByTagName('a'); 290 for(var i=0; i < links.length; i++){ 291 if(links[i].className.indexOf('JSnocheck') == -1){ 292 links[i].onclick = function(){ 293 var rc = changeCheck(msg); 294 if(window.event) window.event.returnValue = rc; 295 return rc; 296 }; 297 } 298 } 299 // add change check for forms 300 var forms = document.forms; 301 for(i=0; i < forms.length; i++){ 302 if(forms[i].className.indexOf('JSnocheck') == -1){ 303 forms[i].onsubmit = function(){ 304 var rc = changeCheck(msg); 305 if(window.event) window.event.returnValue = rc; 306 return rc; 307 }; 308 } 309 } 310 311 // reset change memory var on submit 312 var btn_save = document.getElementById('edbtn__save'); 313 btn_save.onclick = function(){ textChanged = false; }; 314 var btn_prev = document.getElementById('edbtn__preview'); 315 btn_prev.onclick = function(){ textChanged = false; }; 316 317 // add change memory setter 318 edit_text.onchange = function(){ 319 textChanged = true; //global var 320 summaryCheck(); 321 }; 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