1/** 2 * Functions for text editing (toolbar stuff) 3 * 4 * @todo most of the stuff in here should be revamped and then moved to toolbar.js 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,classname){ 16 var btn = document.createElement('button'); 17 var ico = document.createElement('img'); 18 19 // preapare the basic button stuff 20 btn.className = 'toolbutton'; 21 if(classname){ 22 btn.className += ' '+classname; 23 } 24 btn.title = label; 25 if(key){ 26 btn.title += ' ['+key.toUpperCase()+']'; 27 btn.accessKey = key; 28 } 29 30 // set IDs if given 31 if(id){ 32 btn.id = id; 33 ico.id = id+'_ico'; 34 } 35 36 // create the icon and add it to the button 37 if(icon.substr(0,1) == '/'){ 38 ico.src = icon; 39 }else{ 40 ico.src = DOKU_BASE+'lib/images/toolbar/'+icon; 41 } 42 btn.appendChild(ico); 43 44 return btn; 45} 46 47/** 48 * Creates a picker window for inserting text 49 * 50 * The given list can be an associative array with text,icon pairs 51 * or a simple list of text. Style the picker window through the picker 52 * class or the picker buttons with the pickerbutton class. Picker 53 * windows are appended to the body and created invisible. 54 * 55 * @param string id the ID to assign to the picker 56 * @param array props the properties for the picker 57 * @param string edid the ID of the textarea 58 * @rteurn DOMobject the created picker 59 * @author Andreas Gohr <andi@splitbrain.org> 60 */ 61function createPicker(id,props,edid){ 62 var icobase = props['icobase']; 63 var list = props['list']; 64 65 // create the wrapping div 66 var picker = document.createElement('div'); 67 picker.className = 'picker'; 68 if(props['class']){ 69 picker.className += ' '+props['class']; 70 } 71 picker.id = id; 72 picker.style.position = 'absolute'; 73 picker.style.marginLeft = '-10000px'; // no display:none, to keep access keys working 74 picker.style.marginTop = '-10000px'; 75 76 for(var key in list){ 77 if (!list.hasOwnProperty(key)) continue; 78 79 if(isNaN(key)){ 80 // associative array -> treat as image/value pairs 81 var btn = document.createElement('button'); 82 btn.className = 'pickerbutton'; 83 var ico = document.createElement('img'); 84 if(list[key].substr(0,1) == '/'){ 85 ico.src = list[key]; 86 }else{ 87 ico.src = DOKU_BASE+'lib/images/'+icobase+'/'+list[key]; 88 } 89 btn.title = key; 90 btn.appendChild(ico); 91 addEvent(btn,'click',bind(pickerInsert,key,edid)); 92 picker.appendChild(btn); 93 }else if(isString(list[key])){ 94 // a list of text -> treat as text picker 95 var btn = document.createElement('button'); 96 btn.className = 'pickerbutton'; 97 var txt = document.createTextNode(list[key]); 98 btn.title = list[key]; 99 btn.appendChild(txt); 100 addEvent(btn,'click',bind(pickerInsert,list[key],edid)); 101 picker.appendChild(btn); 102 }else{ 103 // a list of lists -> treat it as subtoolbar 104 initToolbar(picker,edid,list); 105 break; // all buttons handled already 106 } 107 108 } 109 var body = document.getElementsByTagName('body')[0]; 110 body.appendChild(picker); 111 return picker; 112} 113 114/** 115 * Called by picker buttons to insert Text and close the picker again 116 * 117 * @author Andreas Gohr <andi@splitbrain.org> 118 */ 119function pickerInsert(text,edid){ 120 insertAtCarret(edid,text); 121 pickerClose(); 122} 123 124/** 125 * Add button action for signature button 126 * 127 * @param DOMElement btn Button element to add the action to 128 * @param array props Associative array of button properties 129 * @param string edid ID of the editor textarea 130 * @return boolean If button should be appended 131 * @author Gabriel Birke <birke@d-scribe.de> 132 */ 133function addBtnActionSignature(btn, props, edid) { 134 if(typeof(SIG) != 'undefined' && SIG != ''){ 135 addEvent(btn,'click',bind(insertAtCarret,edid,SIG)); 136 return true; 137 } 138 return false; 139} 140 141/** 142 * Make intended formattings easier to handle 143 * 144 * Listens to all key inputs and handle indentions 145 * of lists and code blocks 146 * 147 * Currently handles space, backspce and enter presses 148 * 149 * @author Andreas Gohr <andi@splitbrain.org> 150 * @fixme handle tabs 151 */ 152function keyHandler(e){ 153 if(e.keyCode != 13 && 154 e.keyCode != 8 && 155 e.keyCode != 32) return; 156 var field = e.target; 157 var selection = getSelection(field); 158 if(selection.getLength()) return; //there was text selected, keep standard behavior 159 var search = "\n"+field.value.substr(0,selection.start); 160 var linestart = Math.max(search.lastIndexOf("\n"), 161 search.lastIndexOf("\r")); //IE workaround 162 search = search.substr(linestart); 163 164 165 if(e.keyCode == 13){ // Enter 166 // keep current indention for lists and code 167 var match = search.match(/(\n +([\*-] ?)?)/); 168 if(match){ 169 var scroll = field.scrollHeight; 170 var match2 = search.match(/^\n +[\*-]\s*$/); 171 // Cancel list if the last item is empty (i. e. two times enter) 172 if (match2 && field.value.substr(selection.start).match(/^($|\r?\n)/)) { 173 field.value = field.value.substr(0, linestart) + "\n" + 174 field.value.substr(selection.start); 175 selection.start = linestart + 1; 176 selection.end = linestart + 1; 177 setSelection(selection); 178 } else { 179 insertAtCarret(field.id,match[1]); 180 } 181 field.scrollTop += (field.scrollHeight - scroll); 182 e.preventDefault(); // prevent enter key 183 return false; 184 } 185 }else if(e.keyCode == 8){ // Backspace 186 // unindent lists 187 var match = search.match(/(\n +)([*-] ?)$/); 188 if(match){ 189 var spaces = match[1].length-1; 190 191 if(spaces > 3){ // unindent one level 192 field.value = field.value.substr(0,linestart)+ 193 field.value.substr(linestart+2); 194 selection.start = selection.start - 2; 195 selection.end = selection.start; 196 }else{ // delete list point 197 field.value = field.value.substr(0,linestart)+ 198 field.value.substr(selection.start); 199 selection.start = linestart; 200 selection.end = linestart; 201 } 202 setSelection(selection); 203 e.preventDefault(); // prevent backspace 204 return false; 205 } 206 }else if(e.keyCode == 32){ // Space 207 // intend list item 208 var match = search.match(/(\n +)([*-] )$/); 209 if(match){ 210 field.value = field.value.substr(0,linestart)+' '+ 211 field.value.substr(linestart); 212 selection.start = selection.start + 2; 213 selection.end = selection.start; 214 setSelection(selection); 215 e.preventDefault(); // prevent space 216 return false; 217 } 218 } 219} 220 221//FIXME consolidate somewhere else 222addInitEvent(function(){ 223 var field = $('wiki__text'); 224 if(!field) return; 225 // in Firefox, keypress doesn't send the correct keycodes, 226 // in Opera, the default of keydown can't be prevented 227 if (is_opera) { 228 addEvent(field,'keypress',keyHandler); 229 } else { 230 addEvent(field,'keydown',keyHandler); 231 } 232}); 233 234/** 235 * Determine the current section level while editing 236 * 237 * @author Andreas Gohr <gohr@cosmocode.de> 238 */ 239function currentHeadlineLevel(textboxId){ 240 var field = $(textboxId); 241 var selection = getSelection(field); 242 var search = "\n"+field.value.substr(0,selection.start); 243 var lasthl = search.lastIndexOf("\n=="); 244 if(lasthl == -1 && field.form.prefix){ 245 // we need to look in prefix context 246 search = field.form.prefix.value; 247 lasthl = search.lastIndexOf("\n=="); 248 } 249 search = search.substr(lasthl+1,6); 250 251 if(search == '======') return 1; 252 if(search.substr(0,5) == '=====') return 2; 253 if(search.substr(0,4) == '====') return 3; 254 if(search.substr(0,3) == '===') return 4; 255 if(search.substr(0,2) == '==') return 5; 256 257 return 0; 258} 259 260 261/** 262 * global var used for not saved yet warning 263 */ 264window.textChanged = false; 265 266/** 267 * Delete the draft before leaving the page 268 */ 269function deleteDraft() { 270 if (is_opera) return; 271 if (window.keepDraft) return; 272 273 // remove a possibly saved draft using ajax 274 var dwform = $('dw__editform'); 275 if(dwform){ 276 var params = 'call=draftdel'; 277 params += '&id='+encodeURIComponent(dwform.elements.id.value); 278 279 var sackobj = new sack(DOKU_BASE + 'lib/exe/ajax.php'); 280 // this needs to be synchronous and GET to not be aborted upon page unload 281 sackobj.asynchronous = false; 282 sackobj.method = 'GET'; 283 sackobj.AjaxFailedAlert = ''; 284 sackobj.encodeURIString = false; 285 sackobj.runAJAX(params); 286 } 287} 288 289/** 290 * Activate "not saved" dialog, add draft deletion to page unload, 291 * add handlers to monitor changes 292 * 293 * Sets focus to the editbox as well 294 */ 295addInitEvent(function (){ 296 var editform = $('dw__editform'); 297 if (!editform) return; 298 299 var edit_text = $('wiki__text'); 300 if(edit_text) { 301 if(edit_text.readOnly) return; 302 303 // set focus 304 edit_text.focus(); 305 } 306 307 var checkfunc = function(){ 308 window.textChanged = true; //global var 309 summaryCheck(); 310 }; 311 addEvent(editform, 'change', checkfunc); 312 addEvent(editform, 'keydown', checkfunc); 313 314 window.onbeforeunload = function(){ 315 if(window.textChanged) { 316 return LANG.notsavedyet; 317 } 318 }; 319 window.onunload = deleteDraft; 320 321 // reset change memory var on submit 322 addEvent($('edbtn__save'), 'click', function(){ 323 window.onbeforeunload = ''; 324 window.textChanged = false; 325 }); 326 addEvent($('edbtn__preview'), 'click', function(){ 327 window.onbeforeunload = ''; 328 window.textChanged = false; 329 window.keepDraft = true; // needed to keep draft on page unload 330 }); 331 332 var summary = $('edit__summary'); 333 addEvent(summary, 'change', summaryCheck); 334 addEvent(summary, 'keyup', summaryCheck); 335 if (window.textChanged) summaryCheck(); 336}); 337 338/** 339 * Checks if a summary was entered - if not the style is changed 340 * 341 * @author Andreas Gohr <andi@splitbrain.org> 342 */ 343function summaryCheck(){ 344 var sum = document.getElementById('edit__summary'); 345 if(sum.value === ''){ 346 sum.className='missing'; 347 }else{ 348 sum.className='edit'; 349 } 350} 351 352