Bastler / Ponycrush Smileybar

// ==UserScript==
// @name Ponycrush Smileybar
// @namespace rainer-juergensen.de
// @author Bastler
// @version 0.0.2.3
// @description Adds a Smileybar to Ponycrush.com
// @include https://ponycrush.com/*
// @icon https://ponycrush.com/_/img/emotes/rainbowlaugh.png
// @grant none
// @license MIT
// @run-at document-idle
// ==/UserScript==

/*
 * List of known smileys. Just add new ones here and they will get a button.
 */
var smileys = [{
    code: " :D ",
    url: "https://ponycrush.com/_/img/emotes/pinkiehappy.png"
  },
  {
    code: " :) ",
    url: "https://ponycrush.com/_/img/emotes/twilightsmile.png"
  },
  {
    code: " ;) ",
    url: "https://ponycrush.com/_/img/emotes/raritywink.png"
  },
  {
    code: " :P ",
    url: "https://ponycrush.com/_/img/emotes/rainbowwild.png"
  },
  {
    code: " :lol: ",
    url: "https://ponycrush.com/_/img/emotes/rainbowlaugh.png"
  },
  {
    code: " :insane: ",
    url: "https://ponycrush.com/_/img/emotes/pinkiecrazy.png"
  },
  {
    code: " :blush: ",
    url: "https://ponycrush.com/_/img/emotes/twilightblush.png"
  },
  //{code: " <3 ", url:"https://ponycrush.com/_/img/emotes/heart.png"},
  {
    code: " :O ",
    url: "https://ponycrush.com/_/img/emotes/pinkiegasp.png"
  },
  {
    code: " :smug: ",
    url: "https://ponycrush.com/_/img/emotes/ajsmug.png"
  },
  {
    code: " :I ",
    url: "https://ponycrush.com/_/img/emotes/ajbemused.png"
  },
  {
    code: " :/ ",
    url: "https://ponycrush.com/_/img/emotes/applejackunsure.png"
  },
  {
    code: " :( ",
    url: "https://ponycrush.com/_/img/emotes/fluttersad.png"
  },
  {
    code: " x( ",
    url: "https://ponycrush.com/_/img/emotes/twilightangry2.png"
  },
  {
    code: " :V ",
    url: "https://ponycrush.com/_/img/emotes/pacman.png"
  },
  {
    code: " :B ",
    url: "https://ponycrush.com/_/img/emotes/twistnerd.png"
  },
  {
    code: " :awkward: ",
    url: "https://ponycrush.com/_/img/emotes/awkward.png"
  },
  {
    code: " :awyeah: ",
    url: "https://ponycrush.com/_/img/emotes/awyeah.png"
  },
  {
    code: " :blush2: ",
    url: "https://ponycrush.com/_/img/emotes/blush.png"
  },
  {
    code: " :derp: ",
    url: "https://ponycrush.com/_/img/emotes/derp.png"
  },
  {
    code: " :drool: ",
    url: "https://ponycrush.com/_/img/emotes/drool.png"
  },
  {
    code: " :hmmph: ",
    url: "https://ponycrush.com/_/img/emotes/hmmph.png"
  },
  {
    code: " :notamused: ",
    url: "https://ponycrush.com/_/img/emotes/notamused.png"
  },
  {
    code: " :really: ",
    url: "https://ponycrush.com/_/img/emotes/really.png"
  },
  {
    code: " :sad: ",
    url: "https://ponycrush.com/_/img/emotes/sad.png"
  },
  {
    code: " :omg: ",
    url: "https://ponycrush.com/_/img/emotes/shocked.png"
  },
  {
    code: " :smile: ",
    url: "https://ponycrush.com/_/img/emotes/smile.png"
  },
  {
    code: " :stop: ",
    url: "https://ponycrush.com/_/img/emotes/stop.png"
  },
  {
    code: " :tired: ",
    url: "https://ponycrush.com/_/img/emotes/tired.png"
  },
  {
    code: " :wtf: ",
    url: "https://ponycrush.com/_/img/emotes/wtf.png"
  },
  {
    code: " :yikes: ",
    url: "https://ponycrush.com/_/img/emotes/yikes.png"
  }
];

/*
 * This adds a insertAtCaret function to Input and TextArea elements.
 * Source: http://stackoverflow.com/a/19961519
 */
HTMLInputElement.prototype.insertAtCaret = HTMLTextAreaElement.prototype.insertAtCaret = function (text) {
  text = text || '';
  this.focus();
  if (document.selection) {
    // IE
    var sel = document.selection.createRange();
    sel.text = text;
  }
  else if (this.selectionStart || this.selectionStart === 0) {
    // Others
    var startPos = this.selectionStart;
    var endPos = this.selectionEnd;
    this.value = this.value.substring(0, startPos) +
      text +
      this.value.substring(endPos, this.value.length);
    this.selectionStart = startPos + text.length;
    this.selectionEnd = startPos + text.length;
  }
  else {
    this.value += text;
  }
};

/*
 * This adds a insertAtCaret function to divs (for PMs/Discussions).
 * Source: http://stackoverflow.com/a/6691294
 */
HTMLDivElement.prototype.insertAtCaret = function (html) {
  var sel, range;
  this.focus();
  if (window.getSelection) {
    // IE9 and non-IE
    sel = window.getSelection();
    if (sel.getRangeAt && sel.rangeCount) {
      range = sel.getRangeAt(0);
      range.deleteContents();

      // Range.createContextualFragment() would be useful here but is
      // only relatively recently standardized and is not supported in
      // some browsers (IE9, for one)
      var el = document.createElement("div");
      el.innerHTML = html;
      var frag = document.createDocumentFragment(),
        node, lastNode;
      while ((node = el.firstChild)) {
        lastNode = frag.appendChild(node);
      }
      range.insertNode(frag);

      // Preserve the selection
      if (lastNode) {
        range = range.cloneRange();
        range.setStartAfter(lastNode);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);
      }
    }
  }
  else if (document.selection && document.selection.type != "Control") {
    // IE < 9
    document.selection.createRange().pasteHTML(html);
  }
}

/*
 * This function creates a function,
 *  which adds sContent at the current caret to oTarget.
 */
function addSmiley(sContent, oTarget) {
  return function () {
    oTarget.insertAtCaret(sContent);
  }
}

/*
 * Creates the smileybar for the specific Input or TextArea (oTarget).
 */
function createSmileyBar(oTarget) {
  let oOut = document.createElement("DIV");
  let iLen = smileys.length;
  let i;

  oOut.classList.add("btn-group");

  for (i = 0; i < iLen; ++i) {
    let oButton = document.createElement("BUTTON");
    let oImg = document.createElement("IMG");
    oButton.title = smileys[i].code;
    oButton.classList.add("btn", "btn-default", "btn-sm", "btn-small");
    oButton.tabIndex = "-1";
    oButton.type = "button";
    oButton.addEventListener("click", addSmiley(smileys[i].code, oTarget));

    oImg.title = smileys[i].code;
    oImg.alt = smileys[i].code;
    oImg.classList.add("emote");
    oImg.src = smileys[i].url;

    oButton.appendChild(oImg);
    oOut.appendChild(oButton);
  }
  return oOut;
}

/*
 * Adds smileybars to the website.
 */
function initTextArea() {
  //Discussions and PMs
  let oInputs = document.querySelectorAll(".note-editable");
  let iAreLen = oInputs.length;
  for (let i = 0; i < iAreLen; ++i) {
    let oInput = oInputs[i];
    let oBar = createSmileyBar(oInput);
    let oPanel = oInput.previousElementSibling.previousElementSibling;
    oPanel.appendChild(oBar);
  }

  //Horse Feed Main Input
  oInputs = document.querySelectorAll(".panel-body textarea");
  iAreLen = oInputs.length;
  for (let i = 0; i < iAreLen; ++i) {
    let oInput = oInputs[i];
    let oBar = createSmileyBar(oInput);
    let oSpacer = document.createElement("DIV");

    oSpacer.classList.add("panel-body");
    oSpacer.style.padding = "5px 5px 0 5px";
    oSpacer.appendChild(oBar);

    let oBottomPanel = oInput.nextElementSibling;
    oBottomPanel.insertBefore(oSpacer, oBottomPanel.firstChild);
  }

  //Horse Feed Reply Input
  let oFeed = document.getElementsByClassName("wall-scroll");
  if (oFeed.length > 0) {
    let observer = new MutationObserver(function (mutations) {
      mutations.forEach(function (mutation) {
        if (mutation.addedNodes !== undefined) {
          let iLen = mutation.addedNodes.length;
          for (let i = 0; i < iLen; ++i) {
            let note = mutation.addedNodes[i];
            if (note.nodeType == Node.ELEMENT_NODE && note.className == "wall-item-replyform") {
              let oInput = note.firstChild.firstChild;
              let oBar = createSmileyBar(oInput);
              let oDiv = oInput.nextSibling;
              oBar.classList.add("text-left");
              note.firstChild.insertBefore(oBar, oDiv);
            }
          }
        }
      });
    });
    observer.observe(oFeed[0], {
      childList: true,
      subtree: true
    });
  }
}
initTextArea();