User:Nx/Scripts/LinkSuggest.js
Jump to navigation
Jump to search
Note: After saving, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Go to Menu → Settings (Opera → Preferences on a Mac) and then to Privacy & security → Clear browsing data → Cached images and files.
/*
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.insertBefore(this.test, this.textbox);
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) == "]") ) || ( (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) == "{") && (text.charAt(i - 2) != "{") ) {
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', wgServer + 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 = this.textbox.parentNode.appendChild(this.dropdown);
this.imagePreview = this.textbox.parentNode.appendChild(this.imagePreview);
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.fontFamily = getStyle(this.textbox,'font-family');
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');
//incorporate scrollbar
this.test.style.width = this.textbox.clientWidth + "px";
this.test = this.textbox.parentNode.insertBefore(this.test, this.textbox);
this.test.innerHTML = this.textbox.value.substr(0,caret).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
var top = parseFloat(getStyle(this.test,'height'));
//now get the left position
this.test.style.width = "auto";
this.test.innerHTML = this.textbox.value.substr(caret - charInLine, charInLine - this.originalQuery.length).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
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', wgServer + 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);
}
}
addOnloadHook(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(); });
});