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 += ' [ALT+'+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 ico.src = DOKU_BASE+'lib/images/toolbar/'+icon; 35 btn.appendChild(ico); 36 37 return btn; 38} 39 40/** 41 * Creates a picker window for inserting text 42 * 43 * The given list can be an associative array with text,icon pairs 44 * or a simple list of text. Style the picker window through the picker 45 * class or the picker buttons with the pickerbutton class. Picker 46 * windows are appended to the body and created invisible. 47 * 48 * @author Andreas Gohr <andi@splitbrain.org> 49 */ 50function createPicker(id,list,icobase,edid){ 51 var cnt = list.length; 52 53 var picker = document.createElement('div'); 54 picker.className = 'picker'; 55 picker.id = id; 56 picker.style.position = 'absolute'; 57 picker.style.display = 'none'; 58 59 for(var key in list){ 60 var btn = document.createElement('button'); 61 62 btn.className = 'pickerbutton'; 63 64 // associative array? 65 if(isNaN(key)){ 66 var ico = document.createElement('img'); 67 ico.src = DOKU_BASE+'lib/images/'+icobase+'/'+list[key]; 68 btn.title = key; 69 btn.appendChild(ico); 70 eval("btn.onclick = function(){pickerInsert('"+id+"','"+ 71 jsEscape(key)+"','"+ 72 jsEscape(edid)+"');return false;}"); 73 }else{ 74 var txt = document.createTextNode(list[key]); 75 btn.title = list[key]; 76 btn.appendChild(txt); 77 eval("btn.onclick = function(){pickerInsert('"+id+"','"+ 78 jsEscape(list[key])+"','"+ 79 jsEscape(edid)+"');return false;}"); 80 } 81 82 picker.appendChild(btn); 83 } 84 var body = document.getElementsByTagName('body')[0]; 85 body.appendChild(picker); 86} 87 88/** 89 * Called by picker buttons to insert Text and close the picker again 90 * 91 * @author Andreas Gohr <andi@splitbrain.org> 92 */ 93function pickerInsert(pickerid,text,edid){ 94 // insert 95 insertAtCarret(edid,text); 96 // close picker 97 pobj = document.getElementById(pickerid); 98 pobj.style.display = 'none'; 99} 100 101/** 102 * Show a previosly created picker window 103 * 104 * @author Andreas Gohr <andi@splitbrain.org> 105 */ 106function showPicker(pickerid,btn){ 107 var picker = document.getElementById(pickerid); 108 var x = findPosX(btn); 109 var y = findPosY(btn); 110 if(picker.style.display == 'none'){ 111 picker.style.display = 'block'; 112 picker.style.left = (x+3)+'px'; 113 picker.style.top = (y+btn.offsetHeight+3)+'px'; 114 }else{ 115 picker.style.display = 'none'; 116 } 117} 118 119/** 120 * Create a toolbar 121 * 122 * @param string tbid ID of the element where to insert the toolbar 123 * @param string edid ID of the editor textarea 124 * @param array tb Associative array defining the buttons 125 * @author Andreas Gohr <andi@splitbrain.org> 126 */ 127function initToolbar(tbid,edid,tb){ 128 if(!document.getElementById){ return; } 129 var toolbar = document.getElementById(tbid); 130 var cnt = tb.length; 131 for(var i=0; i<cnt; i++){ 132 // create new button 133 btn = createToolButton(tb[i]['icon'], 134 tb[i]['title'], 135 tb[i]['key']); 136 137 // add button action dependend on type 138 switch(tb[i]['type']){ 139 case 'format': 140 var sample = tb[i]['title']; 141 if(tb[i]['sample']){ sample = tb[i]['sample']; } 142 143 eval("btn.onclick = function(){insertTags('"+ 144 jsEscape(edid)+"','"+ 145 jsEscape(tb[i]['open'])+"','"+ 146 jsEscape(tb[i]['close'])+"','"+ 147 jsEscape(sample)+ 148 "');return false;}"); 149 toolbar.appendChild(btn); 150 break; 151 case 'insert': 152 eval("btn.onclick = function(){insertAtCarret('"+ 153 jsEscape(edid)+"','"+ 154 jsEscape(tb[i]['insert'])+ 155 "');return false;}"); 156 toolbar.appendChild(btn); 157 break; 158 case 'signature': 159 if(typeof(SIG) != 'undefined' && SIG != ''){ 160 eval("btn.onclick = function(){insertAtCarret('"+ 161 jsEscape(edid)+"','"+ 162 jsEscape(SIG)+ 163 "');return false;}"); 164 toolbar.appendChild(btn); 165 } 166 break; 167 case 'picker': 168 createPicker('picker'+i, 169 tb[i]['list'], 170 tb[i]['icobase'], 171 edid); 172 eval("btn.onclick = function(){showPicker('picker"+i+ 173 "',this);return false;}"); 174 toolbar.appendChild(btn); 175 break; 176 case 'mediapopup': 177 eval("btn.onclick = function(){window.open('"+ 178 jsEscape(tb[i]['url']+NS)+"','"+ 179 jsEscape(tb[i]['name'])+"','"+ 180 jsEscape(tb[i]['options'])+ 181 "');return false;}"); 182 toolbar.appendChild(btn); 183 break; 184 } // end switch 185 } // end for 186} 187 188/** 189 * Format selection 190 * 191 * Apply tagOpen/tagClose to selection in textarea, use sampleText instead 192 * of selection if there is none. Copied and adapted from phpBB 193 * 194 * @author phpBB development team 195 * @author MediaWiki development team 196 * @author Andreas Gohr <andi@splitbrain.org> 197 * @author Jim Raynor <jim_raynor@web.de> 198 */ 199function insertTags(edid,tagOpen, tagClose, sampleText) { 200 var txtarea = document.getElementById(edid); 201 // IE 202 if(document.selection && !is_gecko) { 203 var theSelection = document.selection.createRange().text; 204 var replaced = true; 205 if(!theSelection){ 206 replaced = false; 207 theSelection=sampleText; 208 } 209 txtarea.focus(); 210 211 // This has change 212 var text = theSelection; 213 if(theSelection.charAt(theSelection.length - 1) == " "){// exclude ending space char, if any 214 theSelection = theSelection.substring(0, theSelection.length - 1); 215 r = document.selection.createRange(); 216 r.text = tagOpen + theSelection + tagClose + " "; 217 } else { 218 r = document.selection.createRange(); 219 r.text = tagOpen + theSelection + tagClose; 220 } 221 if(!replaced){ 222 r.moveStart('character',-text.length-tagClose.length); 223 r.moveEnd('character',-tagClose.length); 224 } 225 r.select(); 226 // Mozilla 227 } else if(txtarea.selectionStart || txtarea.selectionStart == '0') { 228 replaced = false; 229 var startPos = txtarea.selectionStart; 230 var endPos = txtarea.selectionEnd; 231 if(endPos - startPos){ replaced = true; } 232 var scrollTop=txtarea.scrollTop; 233 var myText = (txtarea.value).substring(startPos, endPos); 234 if(!myText) { myText=sampleText;} 235 if(myText.charAt(myText.length - 1) == " "){ // exclude ending space char, if any 236 subst = tagOpen + myText.substring(0, (myText.length - 1)) + tagClose + " "; 237 } else { 238 subst = tagOpen + myText + tagClose; 239 } 240 txtarea.value = txtarea.value.substring(0, startPos) + subst + 241 txtarea.value.substring(endPos, txtarea.value.length); 242 txtarea.focus(); 243 244 //set new selection 245 if(replaced){ 246 var cPos=startPos+(tagOpen.length+myText.length+tagClose.length); 247 txtarea.selectionStart=cPos; 248 txtarea.selectionEnd=cPos; 249 }else{ 250 txtarea.selectionStart=startPos+tagOpen.length; 251 txtarea.selectionEnd=startPos+tagOpen.length+myText.length; 252 } 253 txtarea.scrollTop=scrollTop; 254 // All others 255 } else { 256 var copy_alertText=alertText; 257 var re1=new RegExp("\\$1","g"); 258 var re2=new RegExp("\\$2","g"); 259 copy_alertText=copy_alertText.replace(re1,sampleText); 260 copy_alertText=copy_alertText.replace(re2,tagOpen+sampleText+tagClose); 261 262 if (sampleText) { 263 text=prompt(copy_alertText); 264 } else { 265 text=""; 266 } 267 if(!text) { text=sampleText;} 268 text=tagOpen+text+tagClose; 269 //append to the end 270 txtarea.value += "\n"+text; 271 272 // in Safari this causes scrolling 273 if(!is_safari) { 274 txtarea.focus(); 275 } 276 277 } 278 // reposition cursor if possible 279 if (txtarea.createTextRange){ 280 txtarea.caretPos = document.selection.createRange().duplicate(); 281 } 282} 283 284/* 285 * Insert the given value at the current cursor position 286 * 287 * @see http://www.alexking.org/index.php?content=software/javascript/content.php 288 */ 289function insertAtCarret(edid,value){ 290 var field = document.getElementById(edid); 291 292 //IE support 293 if (document.selection) { 294 field.focus(); 295 sel = document.selection.createRange(); 296 sel.text = value; 297 //MOZILLA/NETSCAPE support 298 }else if (field.selectionStart || field.selectionStart == '0') { 299 var startPos = field.selectionStart; 300 var endPos = field.selectionEnd; 301 var scrollTop = field.scrollTop; 302 field.value = field.value.substring(0, startPos) + 303 value + 304 field.value.substring(endPos, field.value.length); 305 306 field.focus(); 307 var cPos=startPos+(value.length); 308 field.selectionStart=cPos; 309 field.selectionEnd=cPos; 310 field.scrollTop=scrollTop; 311 } else { 312 field.value += "\n"+value; 313 } 314 // reposition cursor if possible 315 if (field.createTextRange){ 316 field.caretPos = document.selection.createRange().duplicate(); 317 } 318} 319 320 321/** 322 * global var used for not saved yet warning 323 */ 324var textChanged = false; 325 326/** 327 * Check for changes before leaving the page 328 */ 329function changeCheck(msg){ 330 if(textChanged){ 331 return confirm(msg); 332 }else{ 333 return true; 334 } 335} 336 337/** 338 * Add changeCheck to all Links and Forms (except those with a 339 * JSnocheck class), add handlers to monitor changes 340 * 341 * Sets focus to the editbox as well 342 */ 343function initChangeCheck(msg){ 344 if(!document.getElementById){ return false; } 345 // add change check for links 346 var links = document.getElementsByTagName('a'); 347 for(var i=0; i < links.length; i++){ 348 if(links[i].className.indexOf('JSnocheck') == -1){ 349 links[i].onclick = function(){return changeCheck(msg);}; 350 links[i].onkeypress = function(){return changeCheck(msg);}; 351 } 352 } 353 // add change check for forms 354 var forms = document.forms; 355 for(i=0; i < forms.length; i++){ 356 if(forms[i].className.indexOf('JSnocheck') == -1){ 357 forms[i].onsubmit = function(){return changeCheck(msg);}; 358 } 359 } 360 361 // reset change memory var on submit 362 var btn_save = document.getElementById('edbtn__save'); 363 btn_save.onclick = function(){ textChanged = false; }; 364 btn_save.onkeypress = function(){ textChanged = false; }; 365 var btn_prev = document.getElementById('edbtn__preview'); 366 btn_prev.onclick = function(){ textChanged = false; }; 367 btn_prev.onkeypress = function(){ textChanged = false; }; 368 369 // add change memory setter 370 var edit_text = document.getElementById('wiki__text'); 371 edit_text.onchange = function(){ 372 textChanged = true; //global var 373 summaryCheck(); 374 }; 375 edit_text.onkeyup = summaryCheck; 376 var summary = document.getElementById('edit__summary'); 377 summary.onchange = summaryCheck; 378 summary.onkeyup = summaryCheck; 379 380 // set focus 381 edit_text.focus(); 382} 383 384/** 385 * Checks if a summary was entered - if not the style is changed 386 * 387 * @author Andreas Gohr <andi@splitbrain.org> 388 */ 389function summaryCheck(){ 390 var sum = document.getElementById('edit__summary'); 391 if(sum.value === ''){ 392 sum.className='missing'; 393 }else{ 394 sum.className='edit'; 395 } 396} 397 398 399/** 400 * Class managing the timer to display a warning on a expiring lock 401 */ 402function locktimer_class(){ 403 this.sack = null; 404 this.timeout = 0; 405 this.timerID = null; 406 this.lasttime = null; 407 this.msg = ''; 408 this.pageid = ''; 409}; 410var locktimer = new locktimer_class(); 411 locktimer.init = function(timeout,msg){ 412 // init values 413 locktimer.timeout = timeout*1000; 414 locktimer.msg = msg; 415 locktimer.lasttime = new Date(); 416 417 if(!$('dw__editform')) return; 418 locktimer.pageid = $('dw__editform').elements.id.value; 419 if(!locktimer.pageid) return; 420 421 // init ajax component 422 locktimer.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 423 locktimer.sack.AjaxFailedAlert = ''; 424 locktimer.sack.encodeURIString = false; 425 locktimer.sack.onCompletion = locktimer.refreshed; 426 427 // register refresh event 428 addEvent($('dw__editform').elements.wikitext,'keyup',function(){locktimer.refresh();}); 429 430 // start timer 431 locktimer.reset(); 432 }; 433 434 /** 435 * (Re)start the warning timer 436 */ 437 locktimer.reset = function(){ 438 locktimer.clear(); 439 locktimer.timerID = window.setTimeout("locktimer.warning()", locktimer.timeout); 440 }; 441 442 /** 443 * Display the warning about the expiring lock 444 */ 445 locktimer.warning = function(){ 446 locktimer.clear(); 447 alert(locktimer.msg); 448 }; 449 450 /** 451 * Remove the current warning timer 452 */ 453 locktimer.clear = function(){ 454 if(locktimer.timerID !== null){ 455 window.clearTimeout(locktimer.timerID); 456 locktimer.timerID = null; 457 } 458 }; 459 460 /** 461 * Refresh the lock via AJAX 462 * 463 * Called on keypresses in the edit area 464 */ 465 locktimer.refresh = function(){ 466 var now = new Date(); 467 // refresh every minute only 468 if(now.getTime() - locktimer.lasttime.getTime() > 60*1000){ 469 locktimer.sack.runAJAX('call=lock&id='+encodeURI(locktimer.pageid)); 470 locktimer.lasttime = now; 471 } 472 }; 473 474 475 /** 476 * Callback. Resets the warning timer 477 */ 478 locktimer.refreshed = function(){ 479 if(this.response != '1') return; // locking failed 480 locktimer.reset(); 481 }; 482// end of locktimer class functions 483 484