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