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