User:Nx/Scripts/LinkSuggest.js

From RationalWiki
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  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(); });
});