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 if(opener == null){ 296 sel = document.selection.createRange(); 297 }else{ 298 sel = opener.document.selection.createRange(); 299 } 300 sel.text = value; 301 //MOZILLA/NETSCAPE support 302 }else if (field.selectionStart || field.selectionStart == '0') { 303 var startPos = field.selectionStart; 304 var endPos = field.selectionEnd; 305 var scrollTop = field.scrollTop; 306 field.value = field.value.substring(0, startPos) + 307 value + 308 field.value.substring(endPos, field.value.length); 309 310 field.focus(); 311 var cPos=startPos+(value.length); 312 field.selectionStart=cPos; 313 field.selectionEnd=cPos; 314 field.scrollTop=scrollTop; 315 } else { 316 field.value += "\n"+value; 317 } 318 // reposition cursor if possible 319 if (field.createTextRange){ 320 field.caretPos = document.selection.createRange().duplicate(); 321 } 322} 323 324 325/** 326 * global var used for not saved yet warning 327 */ 328var textChanged = false; 329 330/** 331 * Check for changes before leaving the page 332 */ 333function changeCheck(msg){ 334 if(textChanged){ 335 return confirm(msg); 336 }else{ 337 return true; 338 } 339} 340 341/** 342 * Add changeCheck to all Links and Forms (except those with a 343 * JSnocheck class), add handlers to monitor changes 344 * 345 * Sets focus to the editbox as well 346 */ 347function initChangeCheck(msg){ 348 if(!document.getElementById){ return false; } 349 // add change check for links 350 var links = document.getElementsByTagName('a'); 351 for(var i=0; i < links.length; i++){ 352 if(links[i].className.indexOf('JSnocheck') == -1){ 353 links[i].onclick = function(){return changeCheck(msg);}; 354 links[i].onkeypress = function(){return changeCheck(msg);}; 355 } 356 } 357 // add change check for forms 358 var forms = document.forms; 359 for(i=0; i < forms.length; i++){ 360 if(forms[i].className.indexOf('JSnocheck') == -1){ 361 forms[i].onsubmit = function(){return changeCheck(msg);}; 362 } 363 } 364 365 // reset change memory var on submit 366 var btn_save = document.getElementById('edbtn_save'); 367 btn_save.onclick = function(){ textChanged = false; }; 368 btn_save.onkeypress = function(){ textChanged = false; }; 369 var btn_prev = document.getElementById('edbtn_preview'); 370 btn_prev.onclick = function(){ textChanged = false; }; 371 btn_prev.onkeypress = function(){ textChanged = false; }; 372 373 // add change memory setter 374 var edit_text = document.getElementById('wikitext'); 375 edit_text.onchange = function(){ 376 textChanged = true; //global var 377 summaryCheck(); 378 }; 379 edit_text.onkeyup = summaryCheck; 380 var summary = document.getElementById('summary'); 381 summary.onchange = summaryCheck; 382 summary.onkeyup = summaryCheck; 383 384 // set focus 385 edit_text.focus(); 386} 387 388/** 389 * Checks if a summary was entered - if not the style is changed 390 * 391 * @author Andreas Gohr <andi@splitbrain.org> 392 */ 393function summaryCheck(){ 394 var sum = document.getElementById('summary'); 395 if(sum.value === ''){ 396 sum.className='missing'; 397 }else{ 398 sum.className='edit'; 399 } 400} 401 402 403/** 404 * global variable for the locktimer 405 */ 406var locktimerID; 407 408/** 409 * This starts a timer to remind the user of an expiring lock 410 * Accepts the delay in seconds and a text to display. 411 */ 412function init_locktimer(delay,txt){ 413 txt = escapeQuotes(txt); 414 locktimerID = self.setTimeout("locktimer('"+txt+"')", delay*1000); 415} 416 417/** 418 * This stops the timer and displays a message about the expiring lock 419 */ 420function locktimer(txt){ 421 clearTimeout(locktimerID); 422 alert(txt); 423} 424 425 426 427