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