1/*jslint sloppy: true, indent: 4, white: true, browser: true, eqeq: true */ 2/*global jQuery, DOKU_BASE, LANG, DOKU_UHC, getSelection, pasteText */ 3 4 5/** 6 * The Link Wizard 7 * 8 * @author Andreas Gohr <gohr@cosmocode.de> 9 * @author Pierre Spring <pierre.spring@caillou.ch> 10 */ 11var dw_linkwiz = { 12 $wiz: null, 13 entry: null, 14 result: null, 15 timer: null, 16 textArea: null, 17 selected: null, 18 selection: null, 19 20 /** 21 * Initialize the dw_linkwizard by creating the needed HTML 22 * and attaching the eventhandlers 23 */ 24 init: function($editor){ 25 // position relative to the text area 26 var pos = $editor.position(); 27 28 // create HTML Structure 29 dw_linkwiz.$wiz = jQuery(document.createElement('div')) 30 .attr('id','link__wiz') 31 .css({ 32 'position': 'absolute', 33 'top': (pos.top+20)+'px', 34 'left': (pos.left+80)+'px', 35 'margin-left': '-10000px', 36 'margin-top': '-10000px' 37 }) 38 .html( 39 '<div id="link__wiz_header">'+ 40 '<img src="'+DOKU_BASE+'lib/images/close.png" width="16" height="16" align="right" alt="" id="link__wiz_close" />'+ 41 LANG.linkwiz+'</div>'+ 42 '<div>'+LANG.linkto+' <input type="text" class="edit" id="link__wiz_entry" autocomplete="off" /></div>'+ 43 '<div id="link__wiz_result"></div>' 44 ) 45 .addClass('picker'); 46 47 $editor[0].form.parentNode.appendChild(dw_linkwiz.$wiz[0]); 48 dw_linkwiz.textArea = $editor[0]; 49 dw_linkwiz.result = jQuery('#link__wiz_result')[0]; 50 dw_linkwiz.entry = jQuery('#link__wiz_entry')[0]; 51 52 // attach event handlers 53 jQuery('#link__wiz_close').click(dw_linkwiz.hide); 54 jQuery(dw_linkwiz.entry).keyup(dw_linkwiz.onEntry); 55 jQuery(dw_linkwiz.result).click(dw_linkwiz.onResultClick); 56 57 dw_linkwiz.$wiz.draggable({handle: '#link__wiz_header'}); 58 }, 59 60 /** 61 * handle all keyup events in the entry field 62 */ 63 onEntry: function(e){ 64 if(e.keyCode == 37 || e.keyCode == 39){ //left/right 65 return true; //ignore 66 } 67 if(e.keyCode == 27){ 68 dw_linkwiz.hide(); 69 e.preventDefault(); 70 e.stopPropagation(); 71 return false; 72 } 73 if(e.keyCode == 38){ //Up 74 dw_linkwiz.select(dw_linkwiz.selected -1); 75 e.preventDefault(); 76 e.stopPropagation(); 77 return false; 78 } 79 if(e.keyCode == 40){ //Down 80 dw_linkwiz.select(dw_linkwiz.selected +1); 81 e.preventDefault(); 82 e.stopPropagation(); 83 return false; 84 } 85 if(e.keyCode == 13){ //Enter 86 if(dw_linkwiz.selected > -1){ 87 var obj = dw_linkwiz.getResult(dw_linkwiz.selected); 88 if(obj){ 89 var a = jQuery(obj).find('a')[0]; 90 dw_linkwiz.resultClick(a); 91 } 92 }else if(dw_linkwiz.entry.value){ 93 dw_linkwiz.insertLink(dw_linkwiz.entry.value); 94 } 95 96 e.preventDefault(); 97 e.stopPropagation(); 98 return false; 99 } 100 dw_linkwiz.autocomplete(); 101 }, 102 103 /** 104 * Get one of the results by index 105 * 106 * @param int result div to return 107 * @returns DOMObject or null 108 */ 109 getResult: function(num){ 110 var childs = jQuery(dw_linkwiz.result).find('div'); 111 var obj = childs[num]; 112 if(obj){ 113 return obj; 114 }else{ 115 return null; 116 } 117 }, 118 119 /** 120 * Select the given result 121 */ 122 select: function(num){ 123 if(num < 0){ 124 dw_linkwiz.deselect(); 125 return; 126 } 127 128 var obj = dw_linkwiz.getResult(num); 129 if(obj){ 130 dw_linkwiz.deselect(); 131 obj.className += ' selected'; 132 133 // make sure the item is viewable in the scroll view 134 // FIXME check IE compatibility 135 if(obj.offsetTop > dw_linkwiz.result.scrollTop + dw_linkwiz.result.clientHeight){ 136 dw_linkwiz.result.scrollTop += obj.clientHeight; 137 }else if(obj.offsetTop - dw_linkwiz.result.clientHeight < dw_linkwiz.result.scrollTop){ // this works but isn't quite right, fixes welcome 138 dw_linkwiz.result.scrollTop -= obj.clientHeight; 139 } 140 // now recheck - if still not in view, the user used the mouse to scroll 141 if( (obj.offsetTop > dw_linkwiz.result.scrollTop + dw_linkwiz.result.clientHeight) || 142 (obj.offsetTop < dw_linkwiz.result.scrollTop) ){ 143 obj.scrollIntoView(); 144 } 145 146 dw_linkwiz.selected = num; 147 } 148 }, 149 150 /** 151 * deselect a result if any is selected 152 */ 153 deselect: function(){ 154 if(dw_linkwiz.selected > -1){ 155 var obj = dw_linkwiz.getResult(dw_linkwiz.selected); 156 if(obj){ 157 obj.className = obj.className.replace(/ ?selected/,''); 158 } 159 } 160 dw_linkwiz.selected = -1; 161 }, 162 163 /** 164 * Handle clicks in the result set an dispatch them to 165 * resultClick() 166 */ 167 onResultClick: function(e){ 168 if(e.target.tagName != 'A') return; 169 e.stopPropagation(); 170 e.preventDefault(); 171 dw_linkwiz.resultClick(e.target); 172 return false; 173 }, 174 175 /** 176 * Handles the "click" on a given result anchor 177 */ 178 resultClick: function(a){ 179 var L = dw_linkwiz; 180 var id = a.title; 181 if(id == '' || id.substr(id.length-1) == ':'){ 182 L.entry.value = id; 183 L.autocomplete_exec(); 184 }else{ 185 L.entry.value = id; 186 if(a.nextSibling && a.nextSibling.tagName == 'SPAN'){ 187 L.insertLink(a.nextSibling.innerHTML); 188 }else{ 189 L.insertLink(''); 190 } 191 } 192 }, 193 194 /** 195 * Insert the id currently in the entry box to the textarea, 196 * replacing the current selection or at the cursor position. 197 * When no selection is available the given title will be used 198 * as link title instead 199 */ 200 insertLink: function(title){ 201 var L = dw_linkwiz; 202 var E = L.entry; 203 if(!E.value) return; 204 205 var sel = getSelection(L.textArea); 206 if(sel.start == 0 && sel.end == 0) sel = L.selection; 207 208 var stxt = sel.getText(); 209 210 // don't include trailing space in selection 211 if(stxt.charAt(stxt.length - 1) == ' '){ 212 sel.end--; 213 stxt = sel.getText(); 214 } 215 216 if(!stxt && !DOKU_UHC) stxt=title; 217 218 // prepend colon inside namespaces for non namespace pages 219 if(L.textArea.form['id'].value.indexOf(':') != -1 && 220 E.value.indexOf(':') == -1){ 221 E.value = ':'+E.value; 222 } 223 224 var link = '[['+E.value+'|'; 225 if(stxt) link += stxt; 226 link += ']]'; 227 228 var so = E.value.length+3; 229 var eo = 2; 230 231 pasteText(sel,link,{startofs: so, endofs: eo}); 232 L.hide(); 233 // reset the entry to the parent namespace and remove : at the beginning 234 E.value = E.value.replace(/(^:)?[^:]*$/, ''); 235 }, 236 237 /** 238 * Start the page/namespace lookup timer 239 * 240 * Calls autocomplete_exec when the timer runs out 241 */ 242 autocomplete: function(){ 243 if(dw_linkwiz.timer !== null){ 244 window.clearTimeout(dw_linkwiz.timer); 245 dw_linkwiz.timer = null; 246 } 247 248 dw_linkwiz.timer = window.setTimeout(dw_linkwiz.autocomplete_exec,350); 249 }, 250 251 /** 252 * Executes the AJAX call for the page/namespace lookup 253 */ 254 autocomplete_exec: function(){ 255 dw_linkwiz.deselect(); 256 dw_linkwiz.result.innerHTML = '<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="" width="16" height="16" />'; 257 258 // because we need to use POST, we 259 // can not use the .load() function. 260 jQuery.post( 261 DOKU_BASE + 'lib/exe/ajax.php', 262 { 263 call: 'linkwiz', 264 q: dw_linkwiz.entry.value 265 }, 266 function (data) { 267 dw_linkwiz.result.innerHTML = data; 268 }, 269 'html' 270 ); 271 }, 272 273 /** 274 * Show the link wizard 275 */ 276 show: function(){ 277 var L = dw_linkwiz; 278 L.selection = getSelection(dw_linkwiz.textArea); 279 L.$wiz.css('marginLeft', '0'); 280 L.$wiz.css('marginTop', '0'); 281 L.entry.focus(); 282 L.autocomplete(); 283 }, 284 285 /** 286 * Hide the link wizard 287 */ 288 hide: function(){ 289 var L = dw_linkwiz; 290 L.$wiz.css('marginLeft', '-10000px'); 291 L.$wiz.css('marginTop', '-10000px'); 292 L.textArea.focus(); 293 }, 294 295 /** 296 * Toggle the link wizard 297 */ 298 toggle: function(){ 299 if(dw_linkwiz.$wiz.css('marginLeft') == '-10000px'){ 300 dw_linkwiz.show(); 301 }else{ 302 dw_linkwiz.hide(); 303 } 304 } 305 306}; 307