User:Nx/LinkSuggest.js
Appearance
< User:Nx
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
![]() | Documentation for this user script can be added at User:Nx/LinkSuggest. |
/*
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(); });
});