EllieFops / L-E's FA Helper

// ==UserScript==
// @name         L-E's FA Helper
// @version      0.4
// @description  Useful tools to improve your FA experience.
// @author       Elizabeth Harper <elliefops@gmail.com>
// @match        https://www.furaffinity.net/*
// @match        http://www.furaffinity.net/*
// @supportURL   https://github.com/EllieFops/FA-Helper/issues
// @homepage     https://github.com/EllieFops/FA-Helper
// @grant        GM_openInTab
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {

var octFAH = {
  component: {},
  core: {},
  http: {},
  module: {},
  util: {}
};
octFAH.component.Component = (function () {
  "use strict";

  var _app;

  function Component(app, element) {
    _app          = app;
    this._element = element;
  }

  Component.prototype.show = function () {
    this._element.style.display = "block";

    return this;
  };

  Component.prototype.hide = function () {
    this._element.style.display = "none";

    return this;
  };

  Component.prototype.topLeft = function (top, left) {
    this._element.style.top  = top.toString() + "px";
    this._element.style.left = left.toString() + "px";

    return this;
  };

  Component.prototype.topRight = function (top, right) {
    this._element.style.top   = top.toString() + "px";
    this._element.style.right = right.toString() + "px";

    return this;
  };

  Component.prototype.bottomLeft = function (bottom, left) {
    this._element.style.bottom = bottom.toString() + "px";
    this._element.style.left   = left.toString() + "px";

    return this;
  };

  Component.prototype.bottomRight = function (bottom, right) {
    this._element.style.bottom = bottom.toString() + "px";
    this._element.style.right  = right.toString() + "px";

    return this;
  };

  Component.prototype.element = function () {
    return this._element;
  };

  return Component;
})();
octFAH.component.HoverView = (function () {
  "use strict";

  var _app, _img, _pane, _settings;

  function HoverView(app) {
    _pane     = document.createElement("DIV");
    _app      = app;
    _img      = document.createElement("IMG");
    _settings = app.getSettings();

    octFAH.component.Component.call(this, app, _pane);
    init(this);
  }

  HoverView.prototype = Object.create(octFAH.component.Component.prototype);

  function handleMouseOver(e) {
    if (!_settings.showPreviews) {
      return;
    }
    _pane.style.display = "block";
    _img.setAttribute("src", e.target.getAttribute("src").replace("@200", "@" + _settings.previewSize.toString()));
  }

  function handleMouseMove(self) {
    return function (e) {
      if (!_settings.showPreviews) {
        return;
      }

      var x, y, i;
      i = _img.getBoundingClientRect();

      x = e.clientX + window.scrollX + 30;
      if (x + i.width > window.innerWidth) {
        x = e.clientX + window.scrollX - i.width - 30;
      }

      y = (e.clientY - i.height / 2) + window.scrollY;

      if (y < window.scrollY) {
        y += window.scrollY - y;
      } else if (y + i.height > window.innerHeight + window.scrollY) {
        y -= y + i.height - (window.innerHeight + window.scrollY);
      }

      self.topLeft(y, x);
    };
  }

  function handleMouseOut() {
    _pane.style.display = "none";
  }

  function init(self) {
    _pane.appendChild(_img);
    _app.getHTMLUtil().style(
      _pane,
      {
        display:  "none",
        position: "absolute",
        zIndex:   10000,
        border:   "2px solid pink"
      }
    );
    document.querySelector("body").appendChild(_pane);
    var i, a;
    a = document.querySelectorAll("b img");
    for (i = 0; i < a.length; i++) {
      a[i].addEventListener("mouseover", handleMouseOver);
      a[i].addEventListener("mouseout", handleMouseOut);
      a[i].addEventListener("mousemove", handleMouseMove(self));
    }
  }

  return HoverView;
})();
octFAH.component.ModalComponent = (function () {

  "use strict";

  var _app;
  var _curtain;

  function ModalComponent(app, element) {
    _app          = app;
    this._element = element;
    octFAH.component.Component.call(this, app, element);

    init(this);
  }

  ModalComponent.prototype = Object.create(octFAH.component.Component.prototype);

  ModalComponent.prototype.show = function () {
    octFAH.component.Component.prototype.show.call(this);
    _curtain.style.display = "block";
  };

  function init(self) {
    if (typeof _curtain === "undefined") {
      initCurtain();
    }
    initElement(self);
  }

  function initCurtain() {
    _curtain = _app
      .wrap("div")
      .style(
      {
        position:   "fixed",
        top:        "0px",
        bottom:     "0px",
        left:       "0px",
        right:      "0px",
        width:      "100%",
        height:     "100%",
        zIndex:     "9999",
        background: "#333",
        opacity:    "0.8",
        display:    "none"
      }
    )
      .attribute("class", "octModal")
      .click(
      function () {
        var i, a;
        a = document.querySelectorAll(".octModal");
        for (i = 0; i < a.length; i++) {
          a[i].style.display = "none";
        }
      }
    )
      .element();

    document.querySelector("body").appendChild(_curtain);
  }

  function initElement(self) {
    var e = self._element;

    _app
      .wrap(e)
      .style(
      {
        position:     "fixed",
        display:      "none",
        zIndex:       "10000",
        border:       "15px solid #666",
        borderRadius: "20px",
        width:        "500px",
        height:       "300px",
        marginTop:    "-155px",
        marginLeft:   "-255px",
        top:          "50%",
        left:         "50%"
      }
    );

    document.querySelector("body").appendChild(e);
  }

  return ModalComponent;
})();
octFAH.component.SettingsMenu = (function () {

  "use strict";

  var _app;
  var _self;

  function SettingsMenu(app) {
    _self = this;
    _app  = app;

    var div = document.createElement("div");

    octFAH.component.ModalComponent.call(this, app, div);

    init(this, div);
  }

  SettingsMenu.prototype = Object.create(octFAH.component.ModalComponent.prototype);

  function init(self, div) {
    initDiv(div);
    initForm(div);
    modUI(self);
  }

  function handleShow(self) {
    return function () {
      self.show();
    };
  }

  function previewSizeChange(e) {
    _app.getSettings().previewSize = e.target.value;
    _app.pushSettings();
  }

  function previewToggle(e) {
    _app.getSettings().showPreviews = e.target.checked;
    _app.pushSettings();
  }

  function modUI(self) {
    var el, but, li;

    but = _app.wrap("a")
      .click(handleShow(self))
      .style({cursor: "pointer", color: "#cfcfcf"})
      .element();

    li = document.createElement("li");
    li.appendChild(but);

    el = document.querySelector("table.block-menu-top ul.dropdown-left li:nth-child(2)");

    if (!el) {
      el = document.querySelector("#nav li:nth-child(4)");
    } else {
      but.style.fontWeight = "bold";
    }

    but.innerHTML = "Helper";

    el.parentNode.insertBefore(li, el);
  }

  function initDiv(div) {
    var title;


    title = _app.wrap("span")
      .html("FA Helper Settings")
      .style(
      {
        fontWeight: "bold",
        fontSize:   "1.2em",
        display:    "block",
        textAlign:  "center",
        padding:    "0 20px 10px 20px"
      }
    );

    _app.wrap(div).addClass("octModal").append(title);
  }

  function initForm(div) {
    var form, util, select, i, labels, opt, check, text, v, sett;

    sett = _app.getSettings();
    util = _app.getHTMLUtil();
    v    = _app.getHelperUtil();

    select = _app
      .wrap("select")
      .attribute("id", "octPrevSizeSel")
      .change(previewSizeChange)
      .element();

    text = _app
      .wrap("textarea")
      .style({width: "300px", height: "5em"})
      .input(
      function () {
        sett.watchShoutText = text.value || "";
        _app.pushSettings();
      }
    )
      .element();

    check = _app
      .wrap(util.makeCheckBox("", "", _app.getSettings().showPreviews))
      .change(previewToggle)
      .element();

    form = _app
      .wrap("form")
      .style({margin:"10px"})
      .append(
      [
        util.makeWrapperLabel("Enable Previews", check),
        util.makeWrapperLabel("Preview Size", select),
        util.makeWrapperLabel("Watcher Auto Shout Text", text)
      ]
    )
      .element();

    if (sett.watchShoutText.length > 0) {
      text.value = sett.watchShoutText;
    } else {
      text.value = "";
    }
    for (i = 200; i <= 400; i += 100) {
      opt = util.makeSelectOption(i, v.toPx(i));
      if (i === sett.previewSize || i.toString() === sett.previewSize.toString()) {
        opt.setAttribute("selected", "selected");
      }
      select.appendChild(opt);
    }
    labels = form.querySelectorAll("label > span");
    for (i = 0; i < labels.length; i++) {
      labels[i].parentNode.style.display      = "block";
      labels[i].parentNode.style.marginBottom = "5px";

      util.style(labels[i], {display: "block", padding: "5px", fontSize: "1.1em", width: "200px"});
    }

    div.appendChild(form);
  }

  return SettingsMenu;
})();
octFAH.component.StatusBox = (function () {

  "use strict";

  var _app;

  function StatusBox(app) {
    _app      = app;
    var outer = document.createElement("div");

    octFAH.component.Component.call(this, app, outer);

    this._completion = 0;
    this._progress   = null;

    init(this, outer);
  }

  StatusBox.prototype = Object.create(octFAH.component.Component.prototype);

  StatusBox.prototype.setCompletion = function (percent) {
    this._completion = percent;
    updateProgress(this);
  };

  StatusBox.prototype.getCompletion = function () {
    return this._completion;
  };

  function init(self, outer) {
    var s;

    makeOuterDiv(outer);
    self._progress = makeProgressBar();

    s = makeProgressContainer();

    s.appendChild(self._progress);
    outer.appendChild(s);
  }

  function makeOuterDiv(div) {
    _app.getHTMLUtil().style(
      div,
      {
        padding:      "25px",
        position:     "fixed",
        border:       "4px solid pink",
        borderRadius: "10px",
        bottom:       "50px",
        right:        "50px",
        display:      "none"
      }
    );

  }

  function makeProgressContainer() {
    var div;

    div = document.createElement("div");
    _app.getHTMLUtil().style(
      div,
      {
        height:       "24px",
        width:        "200px",
        borderRadius: "5px",
        border:       "1px solid #444",
        overflow:     "hidden"
      }
    );

    return div;
  }

  function makeProgressBar() {
    var div;

    div = document.createElement("div");
    _app.getHTMLUtil().style(
      div,
      {
        height:          "100%",
        backgroundColor: "#3379aa"
      }
    );

    return div;
  }

  function updateProgress(con) {
    con._progress.style.width = con.toString() + "%";
  }

  return StatusBox;
})();
octFAH.component.WatchShoutForm = (function () {

  "use strict";

  var _app;

  function WatchShoutForm(application) {
    _app = application;

    this._div = document.createElement("div");

    this._num = null;

    this._check = null;

    this._text = null;

    this._sendButton = null;

    this.shoutText = "";

    octFAH.component.ModalComponent.call(this, _app, this._div);
    this._setupForm();
  }

  WatchShoutForm.prototype = Object.create(octFAH.component.ModalComponent.prototype);

  WatchShoutForm.prototype.setSelCount = function (num) {
    this._num.value = num;
  };

  WatchShoutForm.prototype.getUseDefaultElement = function () {
    return this._check;
  };

  WatchShoutForm.prototype.getShoutTextElement = function () {
    return this._text;
  };

  WatchShoutForm.prototype.getSendButton = function () {
    return this._sendButton;
  };

  WatchShoutForm.prototype._setupForm = function () {

    var sett, util, cLab, tLab, nLab, self;

    self = this;
    util = _app.getHTMLUtil();
    sett = _app.getSettings();

    this.shoutText = "";

    this._num = _app.wrap("input")
      .attributes({disabled: "disabled", type: "number", "default": "0"})
      .style({width: "25px"})
      .element();

    this._text = _app.wrap("textarea")
      .style({width: "200px", height: "8em"})
      .element();

    this._check = util.makeCheckBox("", "", (sett.watchShoutText.length >= 4));

    this._text.value = sett.watchShoutText || "";
    if (this._text.value.length >= 4) {
      this._text.setAttribute("disabled", "disabled");
    }

    this._sendButton = util.makeButton("Send");

    nLab = util.makeWrapperLabel("Selected Watchers", this._num);
    cLab = util.makeWrapperLabel("Use default watcher shout", this._check, false);
    tLab = util.makeWrapperLabel("Shout Text", this._text);

    util.style([tLab.firstElementChild, cLab, tLab, nLab.firstElementChild], {display: "block"});
    util.style([tLab, cLab, nLab], {marginBottom: "5px", fontSize: "1.1em"});

    this._check.addEventListener(
      "change",
      function () {
        if (self._check.checked) {
          self._text.setAttribute("disabled", "disabled");
          self.shoutText   = self._text.value || "";
          self._text.value = sett.watchShoutText;
        } else {
          self._text.removeAttribute("disabled");
          if (self.shoutText.length) {
            self._text.value = self.shoutText;
          }
        }
      }
    );

    _app.wrap(this._div).attribute("id", "octWatchShoutDiv").addClass("octModal").append(
      _app.wrap("form").style({margin: "25px"}).append(
        [
          _app.wrap("p").style({fontSize: "1.1em"}).html("Warning: do not use this on slow connections!").element(),
          nLab,
          cLab,
          tLab,
          _app.wrap("div").style({textAlign: "right"}).append([this._sendButton]).element()
        ]
      )
        .element()
    );
  };

  return WatchShoutForm;
}());
octFAH.core.Application = (function () {
  "use strict";

  var _browserUtil;

  var _browser;

  var _defSet;

  var _helperUtil;

  var _htmlUtil;

  var _location;

  var _module;

  var _self;

  var _settings;

  var _settingsMenu;

  var _storage;

  function Application() {
    _location    = window.location.href;
    _htmlUtil    = new octFAH.util.HTMLUtils(this);
    _helperUtil  = new octFAH.util.Helpers(this);
    _storage     = new octFAH.util.Storage();
    _browserUtil = new octFAH.util.Browser();
    _browser     = _helperUtil.getBrowserType();
    _defSet      = {
      previewSize:    400,
      showPreviews:   true,
      watchShoutText: "",
      favShoutText:   ""
    };
    _settings    = _storage.fetchValue("octFASettings", _defSet);
    _module      = null;
    _self        = this;

    init(this);
  }

  function init(context) {
    updateSettings(context);
    getModule();
    _settingsMenu = new octFAH.component.SettingsMenu(_self);
  }

  Application.prototype.getSettings = function () {
    return _settings;
  };

  Application.prototype.pushSettings = function () {
    _storage.pushValue("octFASettings", _settings);
  };

  Application.prototype.getHTMLUtil = function () {
    return _htmlUtil;
  };

  Application.prototype.getHelperUtil = function () {
    return _helperUtil;
  };

  function getModule() {
    var c, m;

    c = octFAH.core.Config;
    m = octFAH.module;

    if (_location.indexOf(c.subPage) !== -1) {
      _module = new m.SubmissionModule(_self);
    } else if (_location.indexOf(c.browsePage) !== -1) {
      _module = new m.BrowseModule(_self);
    } else if (_location.indexOf(c.searchPage) !== -1) {
      _module = new m.SearchModule(_self);
    } else if (_location.indexOf(c.userPage) !== -1) {
    } else if (_location.indexOf(c.watchesPage) !== -1) {
      _module = new m.MessageModule(_self);
    }
  }

  Application.prototype.makeArtLink = function (i) {
    return octFAH.core.Config.viewPage + i;
  };

  Application.prototype.openArtTab = function (link) {
    _browserUtil.makeNewTab(link, false);
  };

  Application.prototype.getLocation = function () {
    return _location;
  };

  function updateSettings(self) {
    var update, key;

    update = false;

    for (key in _defSet) {
      if (!_defSet.hasOwnProperty(key)) {
        continue;
      }

      if (typeof _settings[key] === "undefined") {
        update         = true;
        _settings[key] = _defSet[key];
      }
    }

    if (update) {
      self.pushSettings();
    }
  }

  Application.prototype.getHelperUtil = function () {
    return _helperUtil;
  };

  Application.prototype.wrap = function (element) {
    return new octFAH.util.HTML(element);
  };

  return Application;
}());
octFAH.core.Config = {
  viewPage:    "//www.furaffinity.net/view/",
  subPage:     "//www.furaffinity.net/msg/submissions/",
  basePage:    "//www.furaffinity.net/",
  browsePage:  "//www.furaffinity.net/browse/",
  userPage:    "//www.furaffinity.net/user/",
  searchPage:  "//www.furaffinity.net/search/",
  watchesPage: "//www.furaffinity.net/msg/others/",
  previewPage: "//t.facdn.net/"
};
octFAH.http.PostRequest = (function () {

  "use strict";

  function PostRequest(u) {
    this.url   = u || "";
    this.data  = "";
    this.load  = [];
    this.error = [];
    this.abort = [];
    this.head  = {};
    this.http  = new XMLHttpRequest();
  }

  PostRequest.prototype.onLoad = function (func) {
    this.load.push(func);
    return this;
  };

  PostRequest.prototype.onError = function (func) {
    this.error.push(func);
    return this;
  };

  PostRequest.prototype.onAbort = function (func) {
    this.abort.push(func);
    return this;
  };

  PostRequest.prototype.onAny = function (func) {
    this.onAbort(func);
    this.onError(func);
    this.onLoad(func);

    return this;
  };

  PostRequest.prototype.setUrl = function (url) {
    this.url = url;
    return this;
  };

  PostRequest.prototype.setData = function (data) {
    this.data = data;
    return this;
  };

  PostRequest.prototype.setHeader = function (key, val) {
    this.head[key] = val;
    return this;
  };

  PostRequest.prototype.send = function () {
    var i;
    this.http.open("POST", this.url);

    for (i in this.head) {
      if (this.head.hasOwnProperty(i)) {
        this.http.setRequestHeader(i, this.head[i]);
      }
    }

    this.http.addEventListener("load", genHandler(this.load, this));
    this.http.addEventListener("abort", genHandler(this.abort, this));
    this.http.addEventListener("error", genHandler(this.error, this));

    this.http.send(this.data);
  };

  function genHandler(backs, self) {
    return function (e) {
      var i;
      for (i = 0; i < backs.length; i++) {
        backs[i](e, self.http);
      }
    };
  }

  return PostRequest;
})();
octFAH.module.BrowseModule = (function ()
{
  "use strict";

  var _app;

  var _hoverView;

  function BrowseModule(app)
  {
    _app = app;
    _hoverView = new octFAH.component.HoverView(_app);
  }

  return BrowseModule;
})();
octFAH.module.MessageModule = (function () {

  "use strict";

  var _app;

  var _charLimit;

  var _selected = [];

  var _shoutForm = null;

  var _watchesForm = null;

  var _outGets = 0;

  var _outPosts = 0;

  var _collected = {};

  function MessageModule(application) {
    _app       = application;
    _charLimit = 222;
    _selected  = [];

    init();
  }

  function init() {
    _watchesForm      = document.getElementById("messages-watches");
    _watchesForm.addEventListener(
      "click", function () {
        fetchSelected();
        _shoutForm.setSelCount(_selected.length);
      }
    );
    _shoutForm = new octFAH.component.WatchShoutForm(_app);
    _shoutForm.getSendButton().addEventListener("click", submitShouts);

    modUI();
  }

  function fetchSelected() {
    var i, butt, ht;

    ht   = [];
    butt = _watchesForm.querySelectorAll("table input:checked");

    for (i = 0; i < butt.length; i++) {
      ht.push(_app.wrap(butt[i]).parent("table").element().querySelector("a").getAttribute("href"));
    }

    _selected = ht;
  }

  function fetchData(url) {
    var req, data;

    data = {key: "", action: "shout", name: ""};

    req = new XMLHttpRequest();
    req.open("GET", url);
    req.responseType = "document";
    req.addEventListener("load", parseForKey(req, data));
    req.addEventListener("abort", subOutGets);
    req.addEventListener("error", subOutGets);
    _outGets++;
    req.send();

    return data;
  }

  function subOutGets() {
    _outGets--;
    send();
  }

  function subOutPosts() {
    _outPosts--;
    removeSelected();
  }

  function parseForKey(req, data) {
    return function () {
      var el;
      try {
        el          = req.responseXML.getElementById("JSForm");
        data.key    = el.querySelector("input[name=key]").getAttribute("value");
        data.name   = el.querySelector("input[name=name]").getAttribute("value");
        data.action = "shout";
      } catch (e) {

      } finally {
        subOutGets();
      }
    };
  }

  function send() {
    var i, text, temp;

    if (_outGets !== 0) {
      return;
    }

    text = _shoutForm.getShoutTextElement().value;

    for (i in _collected) {
      if (!_collected.hasOwnProperty(i)) {continue;}
      temp = _collected[i];

      if (!temp.name || !temp.action || !temp.key) {
        continue;
      }

      submitShout(temp.key, temp.name, temp.action, text);
    }
  }

  function submitShout(key, name, action, shout, func) {
    var post;

    post = new octFAH.http.PostRequest();
    post.setUrl(octFAH.core.Config.userPage + name);
    post.setHeader("Content-type", "application/x-www-form-urlencoded")
      .setData(
      "action=" + action + "&key=" + key + "&name=" + name + "&shout=" + shout + "&chars_left=" + (_charLimit - shout.length).toString()
    );

    if (func) {
      post.onAny(func);
    }

    post.onAny(subOutPosts);
    post.send();
    _outPosts++;
  }

  function modUI() {
    var watches, watchControls, swButt, u;

    u             = _app.getHTMLUtil();
    watches       = document.getElementById("messages-watches");
    watchControls = watches.querySelector("li.section-controls");

    swButt = u.makeButton("Mass Shout Selected", showShoutWatchDiv);
    swButt.setAttribute("class", "button octModalShow");
    watchControls.insertBefore(swButt, watchControls.querySelector("input.remove"));

  }

  function showShoutWatchDiv() {
    _shoutForm.show();
  }

  function submitShouts() {
    var i, l, d;

    l = _selected.length;

    for (i = 0; i < l; i++) {
      try {
        d = fetchData(_selected[i]);
        _collected[d.name] = d;
      } catch (err) {
      }
    }

    _shoutForm.hide();
  }

  function removeSelected() {
    if (_outPosts > 0) {return;}
    _watchesForm.querySelector("input.remove").click();
  }

  return MessageModule;
})();
octFAH.module.SearchModule = (function () {
  "use strict";

  var _app;

  var _hoverView;

  function SearchModule(app) {
    _app       = app;
    _hoverView = new octFAH.component.HoverView(_app);
  }

  return SearchModule;
})();
octFAH.module.SubmissionModule = (function () {
  "use strict";

  var _app;

  var _form;
  var _self;
  var _hoverView;

  function SubmissionModule(app) {
    _app  = app;
    _form = document.getElementById("messages-form");
    _self = this;

    init();
  }

  function init() {
    _hoverView = new octFAH.component.HoverView(_app);
    modSubmissionUI();
  }

  function modSubmissionUI() {
    var forms;
    forms = document.querySelectorAll(".actions");
    for (var i = 0; i < forms.length; i++) {
      forms[i].appendChild(makeTabsButton());
    }
  }

  function makeTabsButton() {
    return _app
      .wrap(_app.getHTMLUtil().makeButton("Load In Tabs", handleTabsButton))
      .attribute("class", "octoTabsButton button")
      .element();
  }

  function handleTabsButton() {
    var boxes = _form.querySelectorAll("input[type=checkbox]:checked");
    var id, i;

    for (i = 0; i < boxes.length; i++) {
      id = parseInt(boxes[i].getAttribute("value"));
      if (id > 0) {
        _app.openArtTab(_app.makeArtLink(id));
      }
    }
  }

  return SubmissionModule;
})();

octFAH.util.HTML = (function () {

  "use strict";

  function HTML(element) {
    this._element = (element instanceof Element) ? element : document.createElement(element);
  }

  HTML.prototype.append = function (children) {
    var i;

    if (children instanceof HTML || children instanceof octFAH.util.HTML) {
      this._element.appendChild(children.element());
    } else if (children instanceof Element) {
      this._element.appendChild(children);
    } else if (children instanceof Array) {
      for (i = 0; i < children.length; i++) {
        this._element.appendChild(children[i]);
      }
    }

    return this;
  };

  HTML.prototype.style = function (style) {
    var key, css, e;
    e = this._element;

    css = e.style;
    for (key in style) {
      if (!style.hasOwnProperty(key)) {
        continue;
      }

      if (typeof css[key] === "undefined") {
        continue;
      }

      css[key] = style[key];
    }

    return this;
  };

  HTML.prototype.parent = function (search) {
    var parent, i;

    parent = this._element.parentNode;

    for (i = 0; i < 300; i++) {
      if (this.matches(search, parent)) {
        return new HTML(parent);
      }

      parent = parent.parentNode;
    }

    return null;
  };

  HTML.prototype.element = function () {
    return this._element;
  };

  HTML.prototype.click = function (func) {
    this._element.addEventListener("click", func);
    return this;
  };

  HTML.prototype.input = function (func) {
    this._element.addEventListener("input", func);
    return this;
  };

  HTML.prototype.change = function (func) {
    this._element.addEventListener("change", func);
    return this;
  };

  HTML.prototype.attribute = function (key, val) {
    if (typeof val === "undefined") {
      return this._element.getAttribute(key);
    }

    this._element.setAttribute(key, val);

    return this;
  };

  HTML.prototype.attributes = function (vals) {
    var a;

    for (a in vals) {
      if (!vals.hasOwnProperty(a)) {
        continue;
      }

      this._element.setAttribute(a, vals[a]);
    }

    return this;
  };

  HTML.prototype.matches = function (selector, ref) {
    if (typeof ref.matches === "function") {
      return ref.matches(selector);
    }

    if (typeof ref.webkitMatchesSelector === "function") {
      return ref.webkitMatchesSelector(selector);
    }

    if (typeof ref.mozMatchesSelector === "function") {
      return ref.mozMatchesSelector(selector);
    }

    if (typeof ref.msMatchesSelector === "function") {
      return ref.msMatchesSelector(selector);
    }

    return false;
  };

  HTML.prototype.addClass = function (c) {
    var a;

    a = this._element.classList;

    if (c instanceof Array) {
      a.add.apply(a, c);
    } else if (typeof c === "string") {
      a.add(c);
    }

    return this;
  };

  HTML.prototype.html = function (inner) {
    if (typeof inner === "undefined") {
      return this._element.innerHTML;
    }

    this._element.innerHTML = inner;
    return this;
  };

  return HTML;
}());
octFAH.util.HTMLUtils = (function () {

  "use strict";

  var _app;

  function HTMLUtils(app) {
    _app  = app;
  }

  HTMLUtils.prototype.append = function (element, children) {
    var i;

    for (i = 0; i < children.length; i++) {
      element.appendChild(children[i]);
    }

    return element;
  };

  HTMLUtils.prototype.style = function (element, style) {
    var key, css, i;

    if (element instanceof Array) {
      for (i = 0; i < element.length; i++) {
        this.style(element[i], style);
      }

      return null;
    }

    css = element.style;
    for (key in style) {
      if (!style.hasOwnProperty(key)) {
        continue;
      }

      if (typeof css[key] === "undefined") {
        continue;
      }

      css[key] = style[key];
    }

    return element;
  };

  HTMLUtils.prototype.makeButton = function (text, click) {
    var el = document.createElement("input");
    el.setAttribute("type", "button");
    el.setAttribute("value", text);

    if (click) {
      el.addEventListener("click", click);
    }

    return el;
  };

  HTMLUtils.prototype.makeCheckBox = function (name, value, checked) {
    var check = document.createElement("input");
    check.setAttribute("type", "checkbox");

    if (name) {
      check.setAttribute("name", name);
    }

    if (value) {
      check.setAttribute("value", value);
    }

    if (checked) {
      check.setAttribute("checked", "checked");
    }

    return check;
  };

  HTMLUtils.prototype.makeSelectOption = function (val, text) {
    var o       = document.createElement("option");
    o.setAttribute("value", val);
    o.innerHTML = text;
    return o;
  };

  HTMLUtils.prototype.makeSpan = function (text) {
    var s       = document.createElement("span");
    s.innerHTML = text;
    return s;
  };

  HTMLUtils.prototype.makeWrapperLabel = function (text, element, before) {
    var label, span;
    before = (typeof before === "undefined") ? true : before;

    label = document.createElement("label");
    span  = document.createElement("span");

    span.innerHTML = text;
    label.appendChild(before ? span : element);
    label.appendChild(before ? element : span);

    return label;
  };

  HTMLUtils.prototype.parent = function (search, ref) {
    var parent, i;

    parent = ref.parentNode;

    for (i = 0; i < 300; i++) {
      if (this.matches(search, parent)) {
        return parent;
      }

      parent = parent.parentNode;
    }

    return null;
  };

  HTMLUtils.prototype.matches = function (selector, ref) {
    if (typeof ref.matches === "function") {
      return ref.matches(selector);
    }

    if (typeof ref.webkitMatchesSelector === "function") {
      return ref.webkitMatchesSelector(selector);
    }

    if (typeof ref.mozMatchesSelector === "function") {
      return ref.mozMatchesSelector(selector);
    }

    if (typeof ref.msMatchesSelector === "function") {
      return ref.msMatchesSelector(selector);
    }

    return false;
  };

  HTMLUtils.prototype.jsonToHTML = function (json) {
    var e, t;

    t = _app.wrap(json.type);

    if (json.id) {
      t.attribute("id", json.id);
    }

    if (json.attributes && typeof json.attributes === "object") {
      t.attributes(json.attributes);
    }

    if (json.classes && json.classes instanceof Array) {
      t.addClass(json.classes);
    }

    if (json.text) {
      t.element.innerHTML = json.text;
    }

    if (json.children && typeof json.children === "object") {
      for (e in json.children) {
        if (!json.children.hasOwnProperty(e)) {
          continue;
        }

        if (json.children[e] instanceof Element) {
          t.element.appendChild(json.children[e]);
        } else {
          t.element.appendChild(this.jsonToHTML(json.children[e]));
        }
      }
    }

    return t.element();
  };

  return HTMLUtils;
})();

octFAH.util.Helpers = (function () {
  "use strict";

  var app;
  var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
  var ARGUMENT_NAMES = /([^\s,]+)/g;

  function Helpers(application) {
    app = application;
  }

  Helpers.prototype.toPx = function (i) {
    return i.toString() + "px";
  };

  Helpers.prototype.getBrowserType = function () {
    var agent, tem, matches;

    agent   = navigator.userAgent;
    matches = agent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];

    if (/trident/i.test(matches[1])) {
      tem = /\brv[ :]+(\d+)/g.exec(agent) || [];
      return {name: "IE", version: (tem[1] || "")};
    }

    if (matches[1] === "Chrome") {
      tem = agent.match(/\bOPR\/(\d+)/);
      if (tem !== null) {
        return {name: "Opera", version: tem[1]};
      }
    }

    matches = matches[2] ? [matches[1], matches[2]] : [navigator.appName, navigator.appVersion, "-?"];

    tem = agent.match(/version\/(\d+)/i);
    if (tem !== null) {
      matches.splice(1, 1, tem[1]);
    }

    return {
      name:    matches[0],
      version: matches[1]
    };
  };

  Helpers.prototype.forEach = function (collection, func) {
    var length, i, key, a, keys, s;

    s = this.getParamNames(func).length;

    if (collection instanceof Array) {
      a      = true;
      length = collection.length;
    } else if (collection instanceof Object) {
      a      = false;
      keys   = Object.keys(collection);
      length = keys.length;
    } else {
      return false;
    }

    for (i = 0; i < length; i++) {
      key = a ? i : keys[i];

      if (s === 1) {
        if (func(collection[key]) === false) {
          return;
        }
      } else if (s > 1) {
        if (func(key, collection[key]) === false) {
          return;
        }
      } else {
        if (func() === false) {
          return;
        }
      }
    }

  };

  Helpers.prototype.getParamNames = function (func) {
    var fnStr, result;

    fnStr  = func.toString().replace(STRIP_COMMENTS, "");
    result = fnStr.slice(fnStr.indexOf("(") + 1, fnStr.indexOf(")")).match(ARGUMENT_NAMES);

    if (result === null) {
      result = [];
    }

    return result;
  };

  return Helpers;
}());

octFAH.util.Browser = (function () {
  "use strict";

  function Browser() {
  }

  Browser.prototype.makeNewTab = function (url, background) {
    GM_openInTab(url, background);
  };

  return Browser;
})();

octFAH.util.Storage = (function () {
  "use strict";

  function Storage() {
  }

  Storage.prototype.fetchValue = function (key, defValue) {
    return GM_getValue(key, defValue);
  };

  Storage.prototype.pushValue = function (key, value) {
    GM_setValue(key, value);
  };

  return Storage;
})();

    var app;
    app= new octFAH.core.Application();
})();