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) 73 +"');return false;}"); 74 }else{ 75 var txt = document.createTextNode(list[key]); 76 btn.title = list[key]; 77 btn.appendChild(txt); 78 eval("btn.onclick = function(){pickerInsert('"+id+"','"+ 79 jsEscape(list[key])+"','"+ 80 jsEscape(edid) 81 +"');return false;}"); 82 } 83 84 picker.appendChild(btn); 85 } 86 var body = document.getElementsByTagName('body')[0]; 87 body.appendChild(picker); 88} 89 90/** 91 * Called by picker buttons to insert Text and close the picker again 92 * 93 * @author Andreas Gohr <andi@splitbrain.org> 94 */ 95function pickerInsert(pickerid,text,edid){ 96 // insert 97 insertAtCarret(edid,text); 98 // close picker 99 pobj = document.getElementById(pickerid); 100 pobj.style.display = 'none'; 101} 102 103/** 104 * Show a previosly created picker window 105 * 106 * @author Andreas Gohr <andi@splitbrain.org> 107 */ 108function showPicker(pickerid,btn){ 109 var picker = document.getElementById(pickerid); 110 var x = findPosX(btn); 111 var y = findPosY(btn); 112 if(picker.style.display == 'none'){ 113 picker.style.display = 'block'; 114 picker.style.left = (x+3)+'px'; 115 picker.style.top = (y+btn.offsetHeight+3)+'px'; 116 }else{ 117 picker.style.display = 'none'; 118 } 119} 120 121/** 122 * Create a toolbar 123 * 124 * @param string tbid ID of the element where to insert the toolbar 125 * @param string edid ID of the editor textarea 126 * @param array tb Associative array defining the buttons 127 * @author Andreas Gohr <andi@splitbrain.org> 128 */ 129function initToolbar(tbid,edid,tb){ 130 if(!document.getElementById) return; 131 var toolbar = document.getElementById(tbid); 132 var cnt = tb.length; 133 for(i=0; i<cnt; i++){ 134 // create new button and add to the toolbar 135 btn = createToolButton(tb[i]['icon'], 136 tb[i]['title'], 137 tb[i]['key']); 138 toolbar.appendChild(btn); 139 140 // add button action dependend on type 141 switch(tb[i]['type']){ 142 case 'format': 143 var sample = tb[i]['title']; 144 if(tb[i]['sample']) sample = tb[i]['sample']; 145 146 eval("btn.onclick = function(){insertTags('"+ 147 jsEscape(edid)+"','"+ 148 jsEscape(tb[i]['open'])+"','"+ 149 jsEscape(tb[i]['close'])+"','"+ 150 jsEscape(sample)+ 151 "');return false;}"); 152 break; 153 case 'insert': 154 eval("btn.onclick = function(){insertAtCarret('"+ 155 jsEscape(edid)+"','"+ 156 jsEscape(tb[i]['insert'])+ 157 "');return false;}"); 158 break; 159 case 'picker': 160 createPicker('picker'+i, 161 tb[i]['list'], 162 tb[i]['icobase'], 163 edid); 164 eval("btn.onclick = function(){showPicker('picker"+i+ 165 "',this);return false;}"); 166 break; 167 case 'popup': 168 eval("btn.onclick = function(){window.open('"+ 169 jsEscape(tb[i]['url'])+"','"+ 170 jsEscape(tb[i]['name'])+"','"+ 171 jsEscape(tb[i]['options'])+ 172 "');return false;}"); 173 break; 174 } // end switch 175 } // end for 176} 177 178/** 179 * Format selection 180 * 181 * Apply tagOpen/tagClose to selection in textarea, use sampleText instead 182 * of selection if there is none. Copied and adapted from phpBB 183 * 184 * @author phpBB development team 185 * @author MediaWiki development team 186 * @author Andreas Gohr <andi@splitbrain.org> 187 * @author Jim Raynor <jim_raynor@web.de> 188 */ 189function insertTags(edid,tagOpen, tagClose, sampleText) { 190 var txtarea = document.getElementById(edid); 191 // IE 192 if(document.selection && !is_gecko) { 193 var theSelection = document.selection.createRange().text; 194 var replaced = true; 195 if(!theSelection){ 196 replaced = false; 197 theSelection=sampleText; 198 } 199 txtarea.focus(); 200 201 // This has change 202 text = theSelection; 203 if(theSelection.charAt(theSelection.length - 1) == " "){// exclude ending space char, if any 204 theSelection = theSelection.substring(0, theSelection.length - 1); 205 r = document.selection.createRange(); 206 r.text = tagOpen + theSelection + tagClose + " "; 207 } else { 208 r = document.selection.createRange(); 209 r.text = tagOpen + theSelection + tagClose; 210 } 211 if(!replaced){ 212 r.moveStart('character',-text.length-tagClose.length); 213 r.moveEnd('character',-tagClose.length); 214 } 215 r.select(); 216 // Mozilla 217 } else if(txtarea.selectionStart || txtarea.selectionStart == '0') { 218 var replaced = false; 219 var startPos = txtarea.selectionStart; 220 var endPos = txtarea.selectionEnd; 221 if(endPos - startPos) replaced = true; 222 var scrollTop=txtarea.scrollTop; 223 var myText = (txtarea.value).substring(startPos, endPos); 224 if(!myText) { myText=sampleText;} 225 if(myText.charAt(myText.length - 1) == " "){ // exclude ending space char, if any 226 subst = tagOpen + myText.substring(0, (myText.length - 1)) + tagClose + " "; 227 } else { 228 subst = tagOpen + myText + tagClose; 229 } 230 txtarea.value = txtarea.value.substring(0, startPos) + subst + 231 txtarea.value.substring(endPos, txtarea.value.length); 232 txtarea.focus(); 233 234 //set new selection 235 if(replaced){ 236 var cPos=startPos+(tagOpen.length+myText.length+tagClose.length); 237 txtarea.selectionStart=cPos; 238 txtarea.selectionEnd=cPos; 239 }else{ 240 txtarea.selectionStart=startPos+tagOpen.length; 241 txtarea.selectionEnd=startPos+tagOpen.length+myText.length; 242 } 243 txtarea.scrollTop=scrollTop; 244 // All others 245 } else { 246 var copy_alertText=alertText; 247 var re1=new RegExp("\\$1","g"); 248 var re2=new RegExp("\\$2","g"); 249 copy_alertText=copy_alertText.replace(re1,sampleText); 250 copy_alertText=copy_alertText.replace(re2,tagOpen+sampleText+tagClose); 251 var text; 252 if (sampleText) { 253 text=prompt(copy_alertText); 254 } else { 255 text=""; 256 } 257 if(!text) { text=sampleText;} 258 text=tagOpen+text+tagClose; 259 //append to the end 260 txtarea.value += "\n"+text; 261 262 // in Safari this causes scrolling 263 if(!is_safari) { 264 txtarea.focus(); 265 } 266 267 } 268 // reposition cursor if possible 269 if (txtarea.createTextRange) txtarea.caretPos = document.selection.createRange().duplicate(); 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) field.caretPos = document.selection.createRange().duplicate(); 308} 309 310 311/** 312 * global var used for not saved yet warning 313 */ 314var textChanged = false; 315 316/** 317 * Check for changes before leaving the page 318 */ 319function changeCheck(msg){ 320 if(textChanged){ 321 return confirm(msg); 322 }else{ 323 return true; 324 } 325} 326 327/** 328 * Add changeCheck to all Links and Forms (except those with a 329 * JSnocheck class), add handlers to monitor changes 330 * 331 * Sets focus to the editbox as well 332 */ 333function initChangeCheck(msg){ 334 if(!document.getElementById) return; 335 // add change check for links 336 var links = document.getElementsByTagName('a'); 337 for(var i=0; i < links.length; i++){ 338 if(links[i].className.indexOf('JSnocheck') == -1){ 339 links[i].onclick = function(){return changeCheck(msg);}; 340 links[i].onkeypress = function(){return changeCheck(msg);}; 341 } 342 } 343 // add change check for forms 344 var forms = document.forms; 345 for(i=0; i < forms.length; i++){ 346 if(forms[i].className.indexOf('JSnocheck') == -1){ 347 forms[i].onsubmit = function(){return changeCheck(msg);}; 348 } 349 } 350 351 // reset change memory var on submit 352 var btn_save = document.getElementById('edbtn_save'); 353 btn_save.onclick = function(){ textChanged = false; }; 354 btn_save.onkeypress = function(){ textChanged = false; }; 355 var btn_prev = document.getElementById('edbtn_preview'); 356 btn_prev.onclick = function(){ textChanged = false; }; 357 btn_prev.onkeypress = function(){ textChanged = false; }; 358 359 // add change memory setter 360 var edit_text = document.getElementById('wikitext'); 361 edit_text.onchange = function(){ 362 textChanged = true; //global var 363 summaryCheck(); 364 } 365 edit_text.onkeyup = summaryCheck; 366 var summary = document.getElementById('summary'); 367 summary.onchange = summaryCheck; 368 summary.onkeyup = summaryCheck; 369 370 // set focus 371 edit_text.focus(); 372} 373 374/** 375 * Checks if a summary was entered - if not the style is changed 376 * 377 * @author Andreas Gohr <andi@splitbrain.org> 378 */ 379function summaryCheck(){ 380 var sum = document.getElementById('summary'); 381 if(sum.value == ''){ 382 sum.className='missing'; 383 }else{ 384 sum.className='edit'; 385 } 386} 387 388 389/** 390 * global variable for the locktimer 391 */ 392var locktimerID; 393 394/** 395 * This starts a timer to remind the user of an expiring lock 396 * Accepts the delay in seconds and a text to display. 397 */ 398function init_locktimer(delay,txt){ 399 txt = escapeQuotes(txt); 400 locktimerID = self.setTimeout("locktimer('"+txt+"')", delay*1000); 401} 402 403/** 404 * This stops the timer and displays a message about the expiring lock 405 */ 406function locktimer(txt){ 407 clearTimeout(locktimerID); 408 alert(txt); 409} 410 411 412 413