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