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 72 for(var key in list){ 73 if (!list.hasOwnProperty(key)) continue; 74 75 if(isNaN(key)){ 76 // associative array -> treat as image/value pairs 77 var btn = document.createElement('button'); 78 btn.className = 'pickerbutton'; 79 var ico = document.createElement('img'); 80 if(list[key].substr(0,1) == '/'){ 81 ico.src = list[key]; 82 }else{ 83 ico.src = DOKU_BASE+'lib/images/'+icobase+'/'+list[key]; 84 } 85 btn.title = key; 86 btn.appendChild(ico); 87 addEvent(btn,'click',bind(pickerInsert,key,edid)); 88 picker.appendChild(btn); 89 }else if(isString(list[key])){ 90 // a list of text -> treat as text picker 91 var btn = document.createElement('button'); 92 btn.className = 'pickerbutton'; 93 var txt = document.createTextNode(list[key]); 94 btn.title = list[key]; 95 btn.appendChild(txt); 96 addEvent(btn,'click',bind(pickerInsert,list[key],edid)); 97 picker.appendChild(btn); 98 }else{ 99 // a list of lists -> treat it as subtoolbar 100 initToolbar(picker,edid,list); 101 break; // all buttons handled already 102 } 103 104 } 105 var body = document.getElementsByTagName('body')[0]; 106 body.appendChild(picker); 107 return picker; 108} 109 110/** 111 * Called by picker buttons to insert Text and close the picker again 112 * 113 * @author Andreas Gohr <andi@splitbrain.org> 114 */ 115function pickerInsert(text,edid){ 116 insertAtCarret(edid,text); 117 pickerClose(); 118} 119 120/** 121 * Add button action for signature button 122 * 123 * @param DOMElement btn Button element to add the action to 124 * @param array props Associative array of button properties 125 * @param string edid ID of the editor textarea 126 * @return boolean If button should be appended 127 * @author Gabriel Birke <birke@d-scribe.de> 128 */ 129function addBtnActionSignature(btn, props, edid) { 130 if(typeof(SIG) != 'undefined' && SIG != ''){ 131 addEvent(btn,'click',bind(insertAtCarret,edid,SIG)); 132 return true; 133 } 134 return false; 135} 136 137/** 138 * Make intended formattings easier to handle 139 * 140 * Listens to all key inputs and handle indentions 141 * of lists and code blocks 142 * 143 * Currently handles space, backspce and enter presses 144 * 145 * @author Andreas Gohr <andi@splitbrain.org> 146 * @fixme handle tabs 147 */ 148function keyHandler(e){ 149 if(e.keyCode != 13 && 150 e.keyCode != 8 && 151 e.keyCode != 32) return; 152 var field = e.target; 153 var selection = getSelection(field); 154 var search = "\n"+field.value.substr(0,selection.start); 155 var linestart = Math.max(search.lastIndexOf("\n"), 156 search.lastIndexOf("\r")); //IE workaround 157 search = search.substr(linestart); 158 159 160 if(e.keyCode == 13){ // Enter 161 // keep current indention for lists and code 162 var match = search.match(/(\n +([\*-] ?)?)/); 163 if(match){ 164 var scroll = field.scrollHeight; 165 insertAtCarret(field.id,match[1]); 166 field.scrollTop += (field.scrollHeight - scroll); 167 e.preventDefault(); // prevent enter key 168 return false; 169 } 170 }else if(e.keyCode == 8){ // Backspace 171 // unindent lists 172 var match = search.match(/(\n +)([*-] ?)$/); 173 if(match){ 174 var spaces = match[1].length-1; 175 176 if(spaces > 3){ // unindent one level 177 field.value = field.value.substr(0,linestart)+ 178 field.value.substr(linestart+2); 179 selection.start = selection.start - 2; 180 selection.end = selection.start; 181 }else{ // delete list point 182 field.value = field.value.substr(0,linestart)+ 183 field.value.substr(selection.start); 184 selection.start = linestart; 185 selection.end = linestart; 186 } 187 setSelection(selection); 188 e.preventDefault(); // prevent backspace 189 return false; 190 } 191 }else if(e.keyCode == 32){ // Space 192 // intend list item 193 var match = search.match(/(\n +)([*-] )$/); 194 if(match){ 195 field.value = field.value.substr(0,linestart)+' '+ 196 field.value.substr(linestart); 197 selection.start = selection.start + 2; 198 selection.end = selection.start; 199 setSelection(selection); 200 e.preventDefault(); // prevent space 201 return false; 202 } 203 } 204} 205 206//FIXME consolidate somewhere else 207addInitEvent(function(){ 208 var field = $('wiki__text'); 209 if(!field) return; 210 addEvent(field,'keydown',keyHandler); 211}); 212 213/** 214 * Determine the current section level while editing 215 * 216 * @author Andreas Gohr <gohr@cosmocode.de> 217 */ 218function currentHeadlineLevel(textboxId){ 219 var field = $(textboxId); 220 var selection = getSelection(field); 221 var search = "\n"+field.value.substr(0,selection.start); 222 var lasthl = search.lastIndexOf("\n=="); 223 if(lasthl == -1 && field.form.prefix){ 224 // we need to look in prefix context 225 search = field.form.prefix.value; 226 lasthl = search.lastIndexOf("\n=="); 227 } 228 search = search.substr(lasthl+1,6); 229 230 if(search == '======') return 1; 231 if(search.substr(0,5) == '=====') return 2; 232 if(search.substr(0,4) == '====') return 3; 233 if(search.substr(0,3) == '===') return 4; 234 if(search.substr(0,2) == '==') return 5; 235 236 return 0; 237} 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 * 278 * @fixme this is old and crappy code. needs to be redone 279 */ 280function initChangeCheck(msg){ 281 var edit_text = document.getElementById('wiki__text'); 282 if(!edit_text) return; 283 if(edit_text.readOnly) return; 284 if(!$('dw__editform')) return; 285 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 edit_text.onchange = function(){ 317 textChanged = true; //global var 318 summaryCheck(); 319 }; 320 var summary = document.getElementById('edit__summary'); 321 addEvent(summary, 'change', summaryCheck); 322 addEvent(summary, 'keyup', summaryCheck); 323 if (textChanged) summaryCheck(); 324 325 // set focus 326 edit_text.focus(); 327} 328 329/** 330 * Checks if a summary was entered - if not the style is changed 331 * 332 * @author Andreas Gohr <andi@splitbrain.org> 333 */ 334function summaryCheck(){ 335 var sum = document.getElementById('edit__summary'); 336 if(sum.value === ''){ 337 sum.className='missing'; 338 }else{ 339 sum.className='edit'; 340 } 341} 342 343 344/** 345 * Class managing the timer to display a warning on a expiring lock 346 */ 347function locktimer_class(){ 348 this.sack = null; 349 this.timeout = 0; 350 this.timerID = null; 351 this.lasttime = null; 352 this.msg = ''; 353 this.pageid = ''; 354}; 355var locktimer = new locktimer_class(); 356 locktimer.init = function(timeout,msg,draft){ 357 // init values 358 locktimer.timeout = timeout*1000; 359 locktimer.msg = msg; 360 locktimer.draft = draft; 361 locktimer.lasttime = new Date(); 362 363 if(!$('dw__editform')) return; 364 locktimer.pageid = $('dw__editform').elements.id.value; 365 if(!locktimer.pageid) return; 366 367 // init ajax component 368 locktimer.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 369 locktimer.sack.AjaxFailedAlert = ''; 370 locktimer.sack.encodeURIString = false; 371 locktimer.sack.onCompletion = locktimer.refreshed; 372 373 // register refresh event 374 addEvent($('dw__editform').elements.wikitext,'keypress',function(){locktimer.refresh();}); 375 376 // start timer 377 locktimer.reset(); 378 }; 379 380 /** 381 * (Re)start the warning timer 382 */ 383 locktimer.reset = function(){ 384 locktimer.clear(); 385 locktimer.timerID = window.setTimeout("locktimer.warning()", locktimer.timeout); 386 }; 387 388 /** 389 * Display the warning about the expiring lock 390 */ 391 locktimer.warning = function(){ 392 locktimer.clear(); 393 alert(locktimer.msg); 394 }; 395 396 /** 397 * Remove the current warning timer 398 */ 399 locktimer.clear = function(){ 400 if(locktimer.timerID !== null){ 401 window.clearTimeout(locktimer.timerID); 402 locktimer.timerID = null; 403 } 404 }; 405 406 /** 407 * Refresh the lock via AJAX 408 * 409 * Called on keypresses in the edit area 410 */ 411 locktimer.refresh = function(){ 412 var now = new Date(); 413 // refresh every minute only 414 if(now.getTime() - locktimer.lasttime.getTime() > 30*1000){ //FIXME decide on time 415 var params = 'call=lock&id='+encodeURIComponent(locktimer.pageid); 416 if(locktimer.draft){ 417 var dwform = $('dw__editform'); 418 params += '&prefix='+encodeURIComponent(dwform.elements.prefix.value); 419 params += '&wikitext='+encodeURIComponent(dwform.elements.wikitext.value); 420 params += '&suffix='+encodeURIComponent(dwform.elements.suffix.value); 421 params += '&date='+encodeURIComponent(dwform.elements.date.value); 422 } 423 locktimer.sack.runAJAX(params); 424 locktimer.lasttime = now; 425 } 426 }; 427 428 429 /** 430 * Callback. Resets the warning timer 431 */ 432 locktimer.refreshed = function(){ 433 var data = this.response; 434 var error = data.charAt(0); 435 data = data.substring(1); 436 437 $('draft__status').innerHTML=data; 438 if(error != '1') return; // locking failed 439 locktimer.reset(); 440 }; 441// end of locktimer class functions 442 443