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 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 'signature': 158 if(SIG!=''){ 159 eval("btn.onclick = function(){insertAtCarret('"+ 160 jsEscape(edid)+"','"+ 161 jsEscape(SIG)+ 162 "');return false;}"); 163 } 164 break; 165 case 'picker': 166 createPicker('picker'+i, 167 tb[i]['list'], 168 tb[i]['icobase'], 169 edid); 170 eval("btn.onclick = function(){showPicker('picker"+i+ 171 "',this);return false;}"); 172 break; 173 case 'mediapopup': 174 eval("btn.onclick = function(){window.open('"+ 175 jsEscape(tb[i]['url']+NS)+"','"+ 176 jsEscape(tb[i]['name'])+"','"+ 177 jsEscape(tb[i]['options'])+ 178 "');return false;}"); 179 break; 180 } // end switch 181 } // end for 182} 183 184/** 185 * Format selection 186 * 187 * Apply tagOpen/tagClose to selection in textarea, use sampleText instead 188 * of selection if there is none. Copied and adapted from phpBB 189 * 190 * @author phpBB development team 191 * @author MediaWiki development team 192 * @author Andreas Gohr <andi@splitbrain.org> 193 * @author Jim Raynor <jim_raynor@web.de> 194 */ 195function insertTags(edid,tagOpen, tagClose, sampleText) { 196 var txtarea = document.getElementById(edid); 197 // IE 198 if(document.selection && !is_gecko) { 199 var theSelection = document.selection.createRange().text; 200 var replaced = true; 201 if(!theSelection){ 202 replaced = false; 203 theSelection=sampleText; 204 } 205 txtarea.focus(); 206 207 // This has change 208 var text = theSelection; 209 if(theSelection.charAt(theSelection.length - 1) == " "){// exclude ending space char, if any 210 theSelection = theSelection.substring(0, theSelection.length - 1); 211 r = document.selection.createRange(); 212 r.text = tagOpen + theSelection + tagClose + " "; 213 } else { 214 r = document.selection.createRange(); 215 r.text = tagOpen + theSelection + tagClose; 216 } 217 if(!replaced){ 218 r.moveStart('character',-text.length-tagClose.length); 219 r.moveEnd('character',-tagClose.length); 220 } 221 r.select(); 222 // Mozilla 223 } else if(txtarea.selectionStart || txtarea.selectionStart == '0') { 224 replaced = false; 225 var startPos = txtarea.selectionStart; 226 var endPos = txtarea.selectionEnd; 227 if(endPos - startPos){ replaced = true; } 228 var scrollTop=txtarea.scrollTop; 229 var myText = (txtarea.value).substring(startPos, endPos); 230 if(!myText) { myText=sampleText;} 231 if(myText.charAt(myText.length - 1) == " "){ // exclude ending space char, if any 232 subst = tagOpen + myText.substring(0, (myText.length - 1)) + tagClose + " "; 233 } else { 234 subst = tagOpen + myText + tagClose; 235 } 236 txtarea.value = txtarea.value.substring(0, startPos) + subst + 237 txtarea.value.substring(endPos, txtarea.value.length); 238 txtarea.focus(); 239 240 //set new selection 241 if(replaced){ 242 var cPos=startPos+(tagOpen.length+myText.length+tagClose.length); 243 txtarea.selectionStart=cPos; 244 txtarea.selectionEnd=cPos; 245 }else{ 246 txtarea.selectionStart=startPos+tagOpen.length; 247 txtarea.selectionEnd=startPos+tagOpen.length+myText.length; 248 } 249 txtarea.scrollTop=scrollTop; 250 // All others 251 } else { 252 var copy_alertText=alertText; 253 var re1=new RegExp("\\$1","g"); 254 var re2=new RegExp("\\$2","g"); 255 copy_alertText=copy_alertText.replace(re1,sampleText); 256 copy_alertText=copy_alertText.replace(re2,tagOpen+sampleText+tagClose); 257 258 if (sampleText) { 259 text=prompt(copy_alertText); 260 } else { 261 text=""; 262 } 263 if(!text) { text=sampleText;} 264 text=tagOpen+text+tagClose; 265 //append to the end 266 txtarea.value += "\n"+text; 267 268 // in Safari this causes scrolling 269 if(!is_safari) { 270 txtarea.focus(); 271 } 272 273 } 274 // reposition cursor if possible 275 if (txtarea.createTextRange){ 276 txtarea.caretPos = document.selection.createRange().duplicate(); 277 } 278} 279 280/* 281 * Insert the given value at the current cursor position 282 * 283 * @see http://www.alexking.org/index.php?content=software/javascript/content.php 284 */ 285function insertAtCarret(edid,value){ 286 var field = document.getElementById(edid); 287 288 //IE support 289 if (document.selection) { 290 field.focus(); 291 if(opener == null){ 292 sel = document.selection.createRange(); 293 }else{ 294 sel = opener.document.selection.createRange(); 295 } 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('wikitext'); 371 edit_text.onchange = function(){ 372 textChanged = true; //global var 373 summaryCheck(); 374 }; 375 edit_text.onkeyup = summaryCheck; 376 var summary = document.getElementById('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('summary'); 391 if(sum.value === ''){ 392 sum.className='missing'; 393 }else{ 394 sum.className='edit'; 395 } 396} 397 398 399/** 400 * global variable for the locktimer 401 */ 402var locktimerID; 403 404/** 405 * This starts a timer to remind the user of an expiring lock 406 * Accepts the delay in seconds and a text to display. 407 */ 408function init_locktimer(delay,txt){ 409 txt = escapeQuotes(txt); 410 locktimerID = self.setTimeout("locktimer('"+txt+"')", delay*1000); 411} 412 413/** 414 * This stops the timer and displays a message about the expiring lock 415 */ 416function locktimer(txt){ 417 clearTimeout(locktimerID); 418 alert(txt); 419} 420 421 422 423