User:Nx/LinkSuggest.js

/* Wikilink autocompletion, based on Wikia's LinkSuggest extension

function getX(element) { var curX = 0; if (obj.offsetParent) { do { curX += obj.offsetLeft; } while (obj = obj.offsetParent); } return curX; }

function getY(element) { var curY = 0; if (obj.offsetParent) { do { curY += obj.offsetTop; } while (obj = obj.offsetParent); } return curY; }

function getStyle(el,styleProp) {   if (window.getComputedStyle) var y = document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp); else if (el.currentStyle) var y = el.currentStyle[styleProp]; return y; }

LinkSuggest = function(_textbox) {

this.textbox = _textbox;

this.dropdown = document.createElement('div'); this.dropdown.style.position = "absolute"; this.dropdown.style.display = "none"; this.dropdown.style.backgroundColor = "lightgrey"; this.dropdown.className = "LinkSuggest_dropdown"; var _this = this; addHandler(this.dropdown,"mouseover",function(event) { _this.mouseOver(event); }); addHandler(this.dropdown,"mouseout",function(event) { _this.mouseOut(event); }); addHandler(this.dropdown,"click",function(event) { _this.click(event); }); var dropdownUl = document.createElement('ul'); this.dropdown.appendChild(dropdownUl); this.dropdown = this.textbox.parentNode.appendChild(this.dropdown); this.imagePreview = document.createElement('div'); this.imagePreview.style.position = "absolute"; this.imagePreview.className = "LinkSuggest_ImagePreview"; this.imagePreview.style.display = "none"; this.imagePreviewImg = document.createElement('img'); this.imagePreviewImg.src = ""; this.imagePreviewImg.style.display = "none"; this.imagePreviewImg = this.imagePreview.appendChild(this.imagePreviewImg); this.imagePreview = this.textbox.parentNode.appendChild(this.imagePreview);

this.test = document.createElement("pre"); this.test = this.textbox.parentNode.appendChild(this.test); this.test.style.visibility = "hidden"; this.test.style.whiteSpace = "pre-wrap"; this.test.style.position = "absolute"; this.test.style.left = "0"; this.test.style.top = "0";

this.originalQuery = "";

this.containerOpen = false; this.selectedIndex = -1; this.numItems = 0;

this.results = [];

this.previewTimer = null;

this.isIgnoreKey = function(nKeyCode) { if ((nKeyCode == 9) || (nKeyCode == 13) || // tab, enter            (nKeyCode == 16) || (nKeyCode == 17) || // shift, ctl            (nKeyCode >= 18 && nKeyCode <= 20) || // alt,pause/break,caps lock            (nKeyCode == 27) || // esc            //(nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end            //(nKeyCode >= 36 && nKeyCode <= 40) || // home,left,up,down,right            //(nKeyCode == 40) || // down            (nKeyCode >= 44 && nKeyCode <= 45)) { // print screen,insert return true; }   return false; };

this.keydown = function(event) { switch (event.keyCode) { case 13: // enter if (this.containerOpen) { if (this.selectedIndex >= 0 && this.selectedIndex < this.numItems) { event.stopPropagation; event.preventDefault; this.autoComplete; } else { this.toggleContainer(false); }     }      break; case 27: // esc this.toggleContainer(false); return; case 38: // up     if (this.containerOpen) { event.stopPropagation; event.preventDefault; this.moveSelection(event.keyCode); }     break; case 40: // down if (this.containerOpen) { event.stopPropagation; event.preventDefault; this.moveSelection(event.keyCode); }     break; } };

this.keyup = function(event) {

if (this.isIgnoreKey(event.keyCode)) { return; }

var text = this.textbox.value.replace(/\r/g, ""); var caret = this.getCaret; var queryStartAt;

// also look forward, to see if we closed this one for(var i = caret; i < text.length; i++) { var c = text.charAt (i) ; if((c == "[") && (text.charAt(i - 1) == "[")) { break ; }   if((c == "]") && (text.charAt(i - 1) == "]")) { return ; }   if((c == "{") && (text.charAt(i - 1) == "{")) { break ; }   if((c == "}") && (text.charAt(i - 1) == "}")) { return ; } }

for(var i = caret; i >= 0; i--) { var c = text.charAt(i); //if(c == "]" || c == "|") { if ( (c == "|") || ( (c == "]") && (text.charAt(i-1) == "]") ) ) { this.toggleContainer(false) ; return; }     //return; //}

if((c == "[") && (text.charAt(i - 1) == "[")) { this.originalQuery = text.substr(i + 1, (caret - i - 1)); queryReal = this.originalQuery;

if (this.originalQuery.indexOf(':')==0){ this.isColon = true; queryReal = queryReal.replace(':',''); } else { this.isColon = false; }     this.isTemplate = false; queryStartAt = i;     break; }

if((c == "{") && (text.charAt(i - 1) == "{")) { this.originalQuery = text.substr(i + 1, (caret - i - 1)); this.isColon = false; if (this.originalQuery.length >= 6 && this.originalQuery.toLowerCase.indexOf('subst:') == 0){ queryReal = "Template:"+this.originalQuery.replace(/subst:/i,''); this.isSubstTemplate = true; } else if (this.originalQuery.indexOf(':')==0){ queryReal = this.originalQuery.replace(':',''); this.isColon = true; } else { queryReal = "Template:"+this.originalQuery; this.isSubstTemplate = false; }     this.isTemplate = true; queryStartAt = i;     break; } }

if(queryStartAt >= 0 && queryReal.length > 2 && this.originalQuery != this.prevQuery ) { this.sendQuery(queryReal); this.prevQuery = this.originalQuery; }

};

this.sendQuery = function(query) { var api = sajax_init_object; if (!api) { return false; } api.open('GET', mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php?format=json&action=opensearch&search=' + encodeURI(query) + '&namespace=0&suggest', true); var _this = this; api.onreadystatechange = function { if(api.readyState==4) { if(api.status==200) { var data = eval('(' + api.responseText + ')'); _this.showSuggestions(data); }   }  };  api.send(null); };

this.showSuggestions = function(data) { this.updatePosition; if (this.selectedIndex >= 0 && this.selectedIndex < this.numItems) { this.unhighlight(this.selectedIndex); } var dropdownUl = this.dropdown.firstChild; while ( dropdownUl.childNodes.length > 0 ) { dropdownUl.removeChild(dropdownUl.firstChild); } if (data.length == 2 && data[1].length > 0) { this.results = data[1]; this.toggleContainer(true); this.numItems = this.results.length; this.selectedIndex = -1; for (var i = 0; i < this.results.length; i++) { var listitem = document.createElement('li'); if (this.isTemplate) { this.results[i] = this.results[i].substring(9); }     listitem.innerHTML = this.results[i]; listitem.index = i;     dropdownUl.appendChild(listitem); } } else { this.toggleContainer(false); } };

this.moveSelection = function(nKeyCode) { if(this.containerOpen) { var newSelection = (nKeyCode == 40) ? (this.selectedIndex + 1) : (this.selectedIndex- 1); if (newSelection < 0 || newSelection >= this.numItems) { return; }   var oldSelection = this.selectedIndex; this.selectedIndex = newSelection; var dropdownUl = this.dropdown.firstChild; if ( oldSelection >= 0 && oldSelection < dropdownUl.childNodes.length ) { dropdownUl.childNodes[oldSelection].className = ""; dropdownUl.childNodes[oldSelection].style.backgroundColor = "transparent"; dropdownUl.childNodes[oldSelection].style.color = "inherit"; this.unhighlight(oldSelection); }   //paranoia if ( newSelection >= dropdownUl.childNodes.length ) { return; }   dropdownUl.childNodes[newSelection].className = "LinkSuggest_selected"; dropdownUl.childNodes[newSelection].style.backgroundColor = "blue"; dropdownUl.childNodes[newSelection].style.color = "white"; this.highlight(newSelection); } };

this.autoComplete = function { this.toggleContainer(false); var result = this.results[this.selectedIndex]; this.textbox.focus;

var scrollTop = this.textbox.scrollTop; var text = this.textbox.value.replace(/\r/g, ""); var caret = this.getCaret;

for(var i = caret; i >= 0; i--) { // break for templates and normal links if( ( ( text.charAt(i - 1) == "[" ) && !this.isTemplate ) || ( ( text.charAt(i - 1) == "{" ) && this.isTemplate ) ) { break; } }

var textBefore = text.substr(0, i);

var newVal = textBefore + ((this.isTemplate && this.isSubstTemplate) ? 'subst:' :  ) + (this.isColon ? ':' : ) + result + (this.isTemplate ? "}}" : "]]") + text.substr(i + this.originalQuery.length); this.textbox.value = newVal;

this.setCaret(i +(this.isColon ? 1 : 0) + ((this.isTemplate && this.isSubstTemplate) ? 6 : 0 ) + result.length + 2); this.textbox.scrollTop = scrollTop; };

this.updatePosition = function { pos = this.getCaretPosition; this.dropdown.style.left=pos[1] + "px"; this.dropdown.style.top=pos[0] + "px"; this.imagePreview.style.left = parseFloat(getStyle(this.dropdown,'left')) + parseFloat(getStyle(this.dropdown,'width')) + "px"; this.imagePreview.style.top = getStyle(this.dropdown,'top');

};

this.toggleContainer = function(show) { if (show) { this.dropdown.style.display="block"; this.containerOpen = true; } else { this.dropdown.style.display="none"; this.containerOpen = false; if (this.selectedIndex >= 0 && this.selectedIndex < this.numItems) { this.unhighlight(this.selectedIndex); } } };

this.getCaret = function { if (typeof(this.textbox.selectionStart) != undefined) { return this.textbox.selectionStart; } else { // hack for IE   this.textbox.focus; var sel = document.selection.createRange; var sel2 = sel.duplicate; sel2.moveToElementText(this.textbox); var caretPos = -1; while(sel2.inRange(sel)) { sel2.moveStart('character'); caretPos++; }   return caretPos; } };

this.setCaret = function(pos) { if(this.textbox.setSelectionRange) { this.textbox.focus; this.textbox.setSelectionRange(pos, pos); } else if (this.textbox.createTextRange) { var range = this.textbox.createTextRange; range.collapse(true); range.moveEnd('character', pos); range.moveStart('character', pos); range.select; } };

this.getCaretPosition = function { var text = this.textbox.value.replace(/\r/g, ""); var caret = this.getCaret; var lineLength = this.getLineLength;

var row = 0; var charInLine = 0; var lastSpaceInLine = 0;

for(i = 0; i < caret; i++) { charInLine++; if(text.charAt(i) == " ") { lastSpaceInLine = charInLine; } else if(text.charAt(i) == "\n") { lastSpaceInLine = 0; charInLine = 0; row++; }   if(charInLine > lineLength) { if(lastSpaceInLine > 0) { charInLine = charInLine - lastSpaceInLine;

lastSpaceInLine = 0; row++; }   }  }

var nextSpace = 0; for(j = caret; j < caret + lineLength; j++) { if(text.charAt(j) == " " || text.charAt(j) == "\n" || caret == text.length) { nextSpace = j;     break; } }

if(nextSpace > lineLength && caret <= lineLength) { charInLine = caret - lastSpaceInLine; row++; }

this.row = row; //hack, since getting the line-height is unreliable this.test.style.fontSize = getStyle(this.textbox,'font-size'); this.test.style.lineHeight = getStyle(this.textbox,'line-height'); this.test.style.marginLeft = getStyle(this.textbox,'margin-left'); this.test.style.marginRight = getStyle(this.textbox,'margin-right'); this.test.style.marginTop = getStyle(this.textbox,'margin-top'); this.test.style.marginBottom = getStyle(this.textbox,'margin-bottom'); this.test.style.paddingLeft = getStyle(this.textbox,'padding-left'); this.test.style.paddingRight = getStyle(this.textbox,'padding-right'); this.test.style.paddingTop = getStyle(this.textbox,'padding-top'); this.test.style.paddingBottom = getStyle(this.textbox,'padding-bottom'); this.test.innerHTML = this.textbox.value.substr(0,caret); var top = parseFloat(getStyle(this.test,'height')); //now get the left position this.test.innerHTML = this.textbox.value.substr(caret - charInLine, charInLine - this.originalQuery.length); var tempsave = this.test.style.display; this.test.style.display="inline"; var left = this.test.offsetWidth; this.test.style.display=tempsave; this.test.innerHTML = "";

//var top = 19+(2+parseFloat(getStyle(this.textbox,'line-height'))*row)-this.textbox.scrollTop; //var left = 3+(8*(charInLine-this.originalQuery.length))-this.textbox.scrollLeft;

left += this.textbox.offsetLeft; top -= this.textbox.scrollTop; top += this.textbox.offsetTop; return [top,left]; };

this.mouseOver = function(event) { var target = this.getTarget(event); if (target.nodeName.toLowerCase == "li") { target.className = "LinkSuggest_selected"; target.style.backgroundColor = "blue"; target.style.color = "white"; if (target.nodeName.toLowerCase == "li" && target.index != null) { if (this.selectedIndex >= 0 && this.selectedIndex < this.numItems) { this.unhighlight(this.selectedIndex); }     this.highlight(target.index); } } };

this.mouseOut = function(event) { var target = this.getTarget(event); if (target.nodeName.toLowerCase == "li") { target.className = ""; target.style.backgroundColor = "transparent"; target.style.color = "inherit"; if (target.nodeName.toLowerCase == "li" && target.index != null) { this.unhighlight(target.index); if (this.selectedIndex >= 0 && this.selectedIndex < this.numItems) { this.highlight(this.selectedIndex); }   }    if (this.selectedIndex >= 0 && this.selectedIndex < this.numItems) { var dropdownUl = this.dropdown.firstChild; dropdownUl.childNodes[this.selectedIndex].className = "LinkSuggest_selected"; dropdownUl.childNodes[this.selectedIndex].style.backgroundColor = "blue"; dropdownUl.childNodes[this.selectedIndex].style.color = "white"; } } };

this.click = function(event) { var target = this.getTarget(event); if (target.nodeName.toLowerCase == "li" && target.index != null) { this.selectedIndex = target.index; this.autoComplete; } };

this.getLineLength = function { return Math.floor(this.textbox.scrollWidth/8); };

this.getTarget = function(ev) { var n = ev.target || ev.srcElement; try { if (n && 3 == n.nodeType) { return n.parentNode; } } catch(e) { }

return n; };

this.highlight = function(index) { if (this.originalQuery.toLowerCase.indexOf('file:') == 0 || this.originalQuery.toLowerCase.indexOf('image:') == 0) { //result always starts with File:, even if you type Image: //var filename = this.results[index].substring(5); var _this = this; this.previewTimer = setTimeout(function {_this.preview(_this.results[index])}, 750); this.imagePreviewImg.style.display = "none"; this.imagePreview.style.left = parseFloat(getStyle(this.dropdown,'left')) + parseFloat(getStyle(this.dropdown,'width')) + "px"; this.imagePreview.style.top = getStyle(this.dropdown,'top'); this.imagePreview.style.display = "block"; if (typeof (injectSpinner) == 'function') injectSpinner (this.imagePreviewImg, 'wpLSPreviewSpinner'); } };

this.unhighlight = function(index) { this.imagePreview.style.display = "none"; this.imagePreviewImg.src = ""; if (typeof (removeSpinner) == 'function') removeSpinner ('wpLSPreviewSpinner'); clearTimeout(this.previewTimer); };

this.preview = function(filename) { if (typeof (removeSpinner) == 'function') removeSpinner ('wpLSPreviewSpinner'); var api = sajax_init_object; if (!api) { return false; } api.open('GET', mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php?format=json&action=query&prop=imageinfo&iiprop=url&titles=' + encodeURI(filename) + '&iiurlwidth=300', true); var _this = this; api.onreadystatechange = function { if(api.readyState==4) { if(api.status==200) { var data = eval('(' + api.responseText + ')'); var url = ""; for (var index in data['query']['pages']) { page = data['query']['pages'][index]; if(typeof(page) !== 'function') { break; }       }        if ( typeof(page['imageinfo'][0]) != undefined ) { url = page['imageinfo'][0]['thumburl']; }       _this.imagePreviewImg.src = url; _this.imagePreviewImg.style.display = 'block'; }   }  };  api.send(null); }

}

$(function {  var textbox = document.getElementById('wpTextbox1');  if (!textbox) {    return;  }  var MyLinkSuggest = new LinkSuggest(textbox);  addHandler(textbox,"keydown",function(event) { MyLinkSuggest.keydown(event); });  addHandler(textbox,"keyup",function(event) { MyLinkSuggest.keyup(event); });  addHandler(textbox,"scroll",function(event) { MyLinkSuggest.updatePosition; }); });