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(i=0; i<cnt; i++){ 132 // create new button and add to the toolbar 133 btn = createToolButton(tb[i]['icon'], 134 tb[i]['title'], 135 tb[i]['key']); 136 toolbar.appendChild(btn); 137 138 // add button action dependend on type 139 switch(tb[i]['type']){ 140 case 'format': 141 var sample = tb[i]['title']; 142 if(tb[i]['sample']){ sample = tb[i]['sample']; } 143 144 eval("btn.onclick = function(){insertTags('"+ 145 jsEscape(edid)+"','"+ 146 jsEscape(tb[i]['open'])+"','"+ 147 jsEscape(tb[i]['close'])+"','"+ 148 jsEscape(sample)+ 149 "');return false;}"); 150 break; 151 case 'insert': 152 eval("btn.onclick = function(){insertAtCarret('"+ 153 jsEscape(edid)+"','"+ 154 jsEscape(tb[i]['insert'])+ 155 "');return false;}"); 156 break; 157 case 'picker': 158 createPicker('picker'+i, 159 tb[i]['list'], 160 tb[i]['icobase'], 161 edid); 162 eval("btn.onclick = function(){showPicker('picker"+i+ 163 "',this);return false;}"); 164 break; 165 case 'popup': 166 eval("btn.onclick = function(){window.open('"+ 167 jsEscape(tb[i]['url'])+"','"+ 168 jsEscape(tb[i]['name'])+"','"+ 169 jsEscape(tb[i]['options'])+ 170 "');return false;}"); 171 break; 172 } // end switch 173 } // end for 174} 175 176/** 177 * Format selection 178 * 179 * Apply tagOpen/tagClose to selection in textarea, use sampleText instead 180 * of selection if there is none. Copied and adapted from phpBB 181 * 182 * @author phpBB development team 183 * @author MediaWiki development team 184 * @author Andreas Gohr <andi@splitbrain.org> 185 * @author Jim Raynor <jim_raynor@web.de> 186 */ 187function insertTags(edid,tagOpen, tagClose, sampleText) { 188 var txtarea = document.getElementById(edid); 189 // IE 190 if(document.selection && !is_gecko) { 191 var theSelection = document.selection.createRange().text; 192 var replaced = true; 193 if(!theSelection){ 194 replaced = false; 195 theSelection=sampleText; 196 } 197 txtarea.focus(); 198 199 // This has change 200 var text = theSelection; 201 if(theSelection.charAt(theSelection.length - 1) == " "){// exclude ending space char, if any 202 theSelection = theSelection.substring(0, theSelection.length - 1); 203 r = document.selection.createRange(); 204 r.text = tagOpen + theSelection + tagClose + " "; 205 } else { 206 r = document.selection.createRange(); 207 r.text = tagOpen + theSelection + tagClose; 208 } 209 if(!replaced){ 210 r.moveStart('character',-text.length-tagClose.length); 211 r.moveEnd('character',-tagClose.length); 212 } 213 r.select(); 214 // Mozilla 215 } else if(txtarea.selectionStart || txtarea.selectionStart == '0') { 216 replaced = false; 217 var startPos = txtarea.selectionStart; 218 var endPos = txtarea.selectionEnd; 219 if(endPos - startPos){ replaced = true; } 220 var scrollTop=txtarea.scrollTop; 221 var myText = (txtarea.value).substring(startPos, endPos); 222 if(!myText) { myText=sampleText;} 223 if(myText.charAt(myText.length - 1) == " "){ // exclude ending space char, if any 224 subst = tagOpen + myText.substring(0, (myText.length - 1)) + tagClose + " "; 225 } else { 226 subst = tagOpen + myText + tagClose; 227 } 228 txtarea.value = txtarea.value.substring(0, startPos) + subst + 229 txtarea.value.substring(endPos, txtarea.value.length); 230 txtarea.focus(); 231 232 //set new selection 233 if(replaced){ 234 var cPos=startPos+(tagOpen.length+myText.length+tagClose.length); 235 txtarea.selectionStart=cPos; 236 txtarea.selectionEnd=cPos; 237 }else{ 238 txtarea.selectionStart=startPos+tagOpen.length; 239 txtarea.selectionEnd=startPos+tagOpen.length+myText.length; 240 } 241 txtarea.scrollTop=scrollTop; 242 // All others 243 } else { 244 var copy_alertText=alertText; 245 var re1=new RegExp("\\$1","g"); 246 var re2=new RegExp("\\$2","g"); 247 copy_alertText=copy_alertText.replace(re1,sampleText); 248 copy_alertText=copy_alertText.replace(re2,tagOpen+sampleText+tagClose); 249 250 if (sampleText) { 251 text=prompt(copy_alertText); 252 } else { 253 text=""; 254 } 255 if(!text) { text=sampleText;} 256 text=tagOpen+text+tagClose; 257 //append to the end 258 txtarea.value += "\n"+text; 259 260 // in Safari this causes scrolling 261 if(!is_safari) { 262 txtarea.focus(); 263 } 264 265 } 266 // reposition cursor if possible 267 if (txtarea.createTextRange){ 268 txtarea.caretPos = document.selection.createRange().duplicate(); 269 } 270} 271 272/* 273 * Insert the given value at the current cursor position 274 * 275 * @see http://www.alexking.org/index.php?content=software/javascript/content.php 276 */ 277function insertAtCarret(edid,value){ 278 var field = document.getElementById(edid); 279 280 //IE support 281 if (document.selection) { 282 field.focus(); 283 if(opener == null){ 284 sel = document.selection.createRange(); 285 }else{ 286 sel = opener.document.selection.createRange(); 287 } 288 sel.text = value; 289 //MOZILLA/NETSCAPE support 290 }else if (field.selectionStart || field.selectionStart == '0') { 291 var startPos = field.selectionStart; 292 var endPos = field.selectionEnd; 293 var scrollTop = field.scrollTop; 294 field.value = field.value.substring(0, startPos) + 295 value + 296 field.value.substring(endPos, field.value.length); 297 298 field.focus(); 299 var cPos=startPos+(value.length); 300 field.selectionStart=cPos; 301 field.selectionEnd=cPos; 302 field.scrollTop=scrollTop; 303 } else { 304 field.value += "\n"+value; 305 } 306 // reposition cursor if possible 307 if (field.createTextRange){ 308 field.caretPos = document.selection.createRange().duplicate(); 309 } 310} 311 312 313/** 314 * global var used for not saved yet warning 315 */ 316var textChanged = false; 317 318/** 319 * Check for changes before leaving the page 320 */ 321function changeCheck(msg){ 322 if(textChanged){ 323 return confirm(msg); 324 }else{ 325 return true; 326 } 327} 328 329/** 330 * Add changeCheck to all Links and Forms (except those with a 331 * JSnocheck class), add handlers to monitor changes 332 * 333 * Sets focus to the editbox as well 334 */ 335function initChangeCheck(msg){ 336 if(!document.getElementById){ return false; } 337 // add change check for links 338 var links = document.getElementsByTagName('a'); 339 for(var i=0; i < links.length; i++){ 340 if(links[i].className.indexOf('JSnocheck') == -1){ 341 links[i].onclick = function(){return changeCheck(msg);}; 342 links[i].onkeypress = function(){return changeCheck(msg);}; 343 } 344 } 345 // add change check for forms 346 var forms = document.forms; 347 for(i=0; i < forms.length; i++){ 348 if(forms[i].className.indexOf('JSnocheck') == -1){ 349 forms[i].onsubmit = function(){return changeCheck(msg);}; 350 } 351 } 352 353 // reset change memory var on submit 354 var btn_save = document.getElementById('edbtn_save'); 355 btn_save.onclick = function(){ textChanged = false; }; 356 btn_save.onkeypress = function(){ textChanged = false; }; 357 var btn_prev = document.getElementById('edbtn_preview'); 358 btn_prev.onclick = function(){ textChanged = false; }; 359 btn_prev.onkeypress = function(){ textChanged = false; }; 360 361 // add change memory setter 362 var edit_text = document.getElementById('wikitext'); 363 edit_text.onchange = function(){ 364 textChanged = true; //global var 365 summaryCheck(); 366 }; 367 edit_text.onkeyup = summaryCheck; 368 var summary = document.getElementById('summary'); 369 summary.onchange = summaryCheck; 370 summary.onkeyup = summaryCheck; 371 372 // set focus 373 edit_text.focus(); 374} 375 376/** 377 * Checks if a summary was entered - if not the style is changed 378 * 379 * @author Andreas Gohr <andi@splitbrain.org> 380 */ 381function summaryCheck(){ 382 var sum = document.getElementById('summary'); 383 if(sum.value === ''){ 384 sum.className='missing'; 385 }else{ 386 sum.className='edit'; 387 } 388} 389 390 391/** 392 * global variable for the locktimer 393 */ 394var locktimerID; 395 396/** 397 * This starts a timer to remind the user of an expiring lock 398 * Accepts the delay in seconds and a text to display. 399 */ 400function init_locktimer(delay,txt){ 401 txt = escapeQuotes(txt); 402 locktimerID = self.setTimeout("locktimer('"+txt+"')", delay*1000); 403} 404 405/** 406 * This stops the timer and displays a message about the expiring lock 407 */ 408function locktimer(txt){ 409 clearTimeout(locktimerID); 410 alert(txt); 411} 412 413 414 415