frisch's UserScript Extender

Extends the document with a new namespace for user script crosswide functions to utilize. Has no use alone but can be accessed by other scripts using document.fExt

Verze ze dne 16. 11. 2016. Zobrazit nejnovější verzi.

// ==UserScript==
// @name         frisch's UserScript Extender
// @namespace    http://null.frisch-live.de/
// @version      0.57
// @description  Extends the document with a new namespace for user script crosswide functions to utilize. Has no use alone but can be accessed by other scripts using document.fExt
// @author       frisch
// @include      *
// @require      http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js
// @grant        GM_setClipboard
// ==/UserScript==
if(document.fExt === undefined) {
    console.log("Initializing frisch's UserScript Extender...");

    // Initialization
    fExt = {};
    fExt.jq = $;
    // Overwrites the contains expression to be case-insensitive
    fExt.jq.expr[':'].contains = function(a, i, m) {
        return fExt.jq(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
    };
    fExt.settings = {
        minShowTime: 3000, // in milliseconds
        maxShowTime: 9000, // in milliseconds
        position: "BottomRight", // Position for the popup menu: TopRight, TopLeft, BottomRight, BottomLeft
        animationType: "fade", // Animation for the popup menu: slide, fade, none
        animationSpeed: 500,
        customContextMenu: true,
        hideContextMenuOnLeave: true, // automatically fades out the custom context menu when it loses focus, otherwhise only hides when you click somewhere
        progressType: "progressbar", //Possible values: hourglass, progressbar
        toleranceX: 20, // pixels as tolerance for the context menu, adjust the position slightly
        toleranceY: 20, // pixels as tolerance for the context menu, adjust the position slightly
    };
    fExt.popupQueue = [];
    fExt.popping = false;

    var hourGlass0 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhkICgkAYfvBAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAKpJREFUSMetlcEOwyAMQ+39/z97h0nrOkjihPZAAYlXVPALcPgQgE7WEzhA8LODKYLfZoTg7dVGcOm0ENx2bQTDgYVgOiwRLCdSBK2pEEHAB6wI2pvaIgj0AReC87SpOtbXEz5Ivq97Zrs/UWvsO8eovTnci6RYPs5VVu6vKkyqFZjFWZ5FI6EYOkiEIlNIgdLUUOJGqmpK+U/rGpSFn8KiYWHiA8X1NM14AxEBKx1JZtGVAAAAAElFTkSuQmCC";
    var hourGlass25 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhkICgR+0Id8AAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAKhJREFUSMetlUkOxCAMBLv4/589h5koG3hjckgAydVtR8bS5oMk24lH+iKsoSx+3zqC++tA1C1cAQ0LT0AewXSZRrDcpBC42xBBeOAiSB0tEUh5wBtB2tQUgVQHnAj63WbRbx3/uA8cfbv3bLWIlxJQT+FQDy4LInUFLkYUHl1ZI6PuIciGrxIZidxdF+TV5y6GH05YC2rqbxc0p8JjsFi/k7aH62436wPGSjUSU11z+AAAAABJRU5ErkJggg==";
    var hourGlass50 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhkICTqUnMkUAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAKpJREFUSMe1lMsOgzAMBHfy///sHopEQsGv0BxAMtpZJ8ErbS4k2Y4e6YuwhrM43nUE66OKmJVM9SRiNWb5lkBc+r4ACi08AZIteAAfQVhwEaRKjwikPOAXQbqpWwRSHXAi6E+bRdc63sgDx9/Wma0e4nQE1LdwyAlmjMhdQRcjkkeRRcbd62L48jhvRuzupx7lPHIizTo/IC35Taxbdwxoy6d7tb8NY2Z9AJxfMxVU9/UgAAAAAElFTkSuQmCC";
    var hourGlass75 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhkICTPtQHGwAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAK1JREFUSMetlUkSgzAMBKf5/5/FIUnFG9ZiOEAVaHqE8Qjp8ECSneiRPggrOIvvNY+gP2URrZLmfhDRG9M9CyCGvgeAi2DSMNVsECwULOoeECzrWVptW4gANi1EATOCcFNLBFIe8EdQT5t5O+t6Yx5s/K3PbHYRmyUg/wo/dycgeO5yurh8+b4LEkkIhsly342k3Bkolt89FOQPQ9Vqe5iifJhxVk/S8c/1NM26AScHLxnJI48dAAAAAElFTkSuQmCC";
    var hourGlass100 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhkICSxgSHxFAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAKpJREFUSMetlUsShDAIBem5/52ZxbhIIhA+40Kr9L0GCSEiwwsR0YkfkR9CG5GF51lHsN+qiNXJ8j6J2AOzfUsgjrwPwBXBy8NLEyAwHBg6B4GpxwxlIHDUONkeCFwtbr0WBIGSoOQPglBHuOiaUl0Ql876/GMejH6BhL1ZRM0pSdqLjaT5fCnYk5tJa+tG0X4ZKFrvHhp2Z6hqr4dp2o8xof2dND5cp7tZvgvYKh27llJDAAAAAElFTkSuQmCC";

    var pbar0 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AkTCSsaEe8UvgAAALZJREFUSMfdll0OxCAIhAfDjfT+J5Az0adumo1FUXSTnacmVj8ZflpSVQAAEaGlnPPnWUTg1X0+v73wBFjro/C0AvGIIwAj0bF1yHNjD+KKqAWZsam1h98gMwDrYrya7BF7Tes8kJGLpRMQACBVBREpNkpVKeGQvhuWIqtQRHQ5R16FgXrDNQzUs/aYdf9X3nR/akspZqJnh2yt9UflPfPj4Sl59vTCSl/xzii29ZHlSIoaMb31C3teW2ty8diEAAAAAElFTkSuQmCC";
    var pbar25 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AkTCSsrQDEUhAAAALZJREFUSMfdlssNxCAMRMfIHUH/FcQ1zZ6yyoEYzG+lnVMkAg+PP4mQBACICGrKOX+fzQxR3efr2wtPgLfeC08zkIh0BaAnOvUOeW5sQUIR1SAjNtX26BtkBOBdTGeT3WOva10E0nOxdAICAEISIkJsFElJOCTtqCIZrUIz43SOoloGag3XZaCWtces+7/ylvtTW0pxEz06ZK/r+lF5j/x4REpeI70w01e6M4ptfeQ5klaNmNb6B29WW2uHr2HMAAAAAElFTkSuQmCC";
    var pbar50 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AkTCSs7XYYE4AAAALVJREFUSMfdlssNxCAMRMfIHZn+Kwg1zZ6yyoEYzG+lnVMkAg+PP4mQBACICGoys+9zKQVR3efr2wtPgLfeC08zkIh0BaAnOvUOeW5sQUIR1SAjNtX26BtkBOBdTGeT3WOva10E0nOxdAICAEISIkJsFElJOKThHJmZdNjL6RxFtQzUGq7LQK3qO2bd/5W33J/anLOb6NEhe13Xj8p75McjUvIa6YWZvtKdUWzrI8+RtGrEtNY/mktYa4XsqhcAAAAASUVORK5CYII=";
    var pbar75 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AkTCSwJ2hDDpwAAAK1JREFUSMftllEOAyEIRAfDjfD+J9AzsV/bbBpFQWuTpvNlgvJwNCipKgCAiNCSiLzGtVZ4defn3oQnwIrPwtMKxCPeAZjZHVtJngtHENeOWpCITa013INEAFZhvHrYM/aa1nkgM4WlExAAIFUFESk+KFWlhEPinclEhN7s1eUz8uoPCuv3rjfdT23O2ewC0SZbSvnSZYh8PDydnXuBnZDtLcgqNp2AuEAjW0fxC+YyVWuwau4lAAAAAElFTkSuQmCC";
    var pbar100 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AkTCTAYVte+CAAAAKJJREFUSMftllEOxCAIRAfDjfT+J9AzzX510w+loLabbDpfJigPR4MKSQCAiKCnnPN33FpDVEd+HU04A6y4F55WIBHpDoBnd2olOS+8goR21IPM2NRboyPIDMAqTFcP22OvaV0E4iksPQEBACEJESFuFElJeEgv6AX9MUiOp7aUYnaB2SZba/2RdTMfj0hn11FgJ2T5mfAAbrl1liNpRxJP/APHYkptRNVvhwAAAABJRU5ErkJggg==";

    var loader0, loader25, loader50, loader75, loader100;

    switch(fExt.settings.progressType){
        case "progressbar":
            loader0 = pbar0;
            loader25 = pbar25;
            loader50 = pbar50;
            loader75 = pbar75;
            loader100 = pbar100;
            break;
        case "hourglass":
            loader0 = hourGlass0;
            loader25 = hourGlass25;
            loader50 = hourGlass50;
            loader75 = hourGlass75;
            loader100 = hourGlass100;
            break;
        default:
            loader0 = hourGlass0;
            loader25 = hourGlass25;
            loader50 = hourGlass50;
            loader75 = hourGlass75;
            loader100 = hourGlass100;
            break;
    }

    // Custom Elements
    fExt.fExtPopup = $('<div id="fExtPopup" class="fExtElement" style="display:none; position:fixed; width:auto; height:auto; background-color:#545454; padding:10px; border-color:white; border-style:groove; color:white; z-index:2147483647; text-align:center; font-size:12px;"></div>');
    $("body").append(fExt.fExtPopup);

    fExt.fExtMessage = $('<div id="fExtMessage" class="fExtElement" style="display:none; position:fixed; width:auto; height:auto; background-color:#545454; padding:25px; border-color:white; border-style:groove; color:white; z-index:2147483647; text-align:center; font-size:large; padding:20px;"></div>');
    fExt.fExtMessageText = $('<span style="display: inline-block;"></span>');
    fExt.fExtMessageText.appendTo(fExt.fExtMessage);
    fExt.fExtMessage.appendTo("body");

    // Functions
    fExt.setLoading = function(percentage){
        if(percentage >= 100){
            $("link[rel='icon']").attr("href", loader100);
            $("link[rel='shortcut icon']").attr("href", loader100);

            setTimeout(function(){ fExt.setLoading(-1); },fExt.settings.minShowTime);
        }
        else if(percentage >= 75){
            $("link[rel='icon']").attr("href", loader75);
            $("link[rel='shortcut icon']").attr("href", loader75);
        }
        else if(percentage >= 50){
            $("link[rel='icon']").attr("href", loader50);
            $("link[rel='shortcut icon']").attr("href", loader50);
        }
        else if(percentage >= 25){
            $("link[rel='icon']").attr("href", loader25);
            $("link[rel='shortcut icon']").attr("href", loader25);
        }
        else if(percentage >= 0){
            $("link[rel='icon']").attr("href", loader0);
            $("link[rel='shortcut icon']").attr("href", loader0);
        }
        else {
            $("link[rel='icon']").attr("href", siteFavIconHref);
            $("link[rel='shortcut icon']").attr("href", siteFavIconHref);
        }
    };

    fExt.enqueuePopup = function(msg) {
        fExt.popupQueue.push(msg);
    };

    fExt.dequeuePopup = function() {
        var rv;
        if(fExt.popupQueue.length > 0){
            rv = fExt.popupQueue[0];
            fExt.popupQueue.splice(0, 1);
        }
        return rv;
    };

    fExt.createStyle = function(newClass) {
        $( "<style>" + newClass + "</style>" ).appendTo("head");
    };

    fExt.center = function (element,w) {
        if(w !== undefined)
            element.width(w);

        element.css("position", "fixed")
            .css("top", (($(window).height() - $(element).outerHeight()) / 2) + "px")
            .css("left", (($(window).width() - $(element).outerWidth()) / 2) + "px");
    };

    fExt.popup = function(msg) {
        if(msg !== undefined){
            if(fExt.popping){
                fExt.enqueuePopup(msg);
                return;
            }
            fExt.popping = true;
            fExt.fExtPopup.text(msg);

            fExt.show(fExt.fExtPopup);

            var showTime = msg.length * 50;

            if(showTime < fExt.settings.minShowTime)
                showTime = fExt.settings.minShowTime;
            else if(showTime > fExt.settings.maxShowTime)
                shotTime = fExt.settings.maxShowTime;

            setTimeout(function(){
                fExt.hide(fExt.fExtPopup);
                setTimeout(function(){
                    fExt.fExtPopup.text('');
                    fExt.popping = false;
                    fExt.popup(fExt.dequeuePopup());
                },fExt.settings.animationSpeed);
            },showTime);
        }
    };

    fExt.clipboard = function(action, text) {
        var clipboardCopy, retVal;
        action = action.toLowerCase();

        clipboardCopy = document.createElement('textarea');
        var jqClipboardCopy = $(clipboardCopy);
        document.body.appendChild(clipboardCopy);
        clipboardCopy.value = text;
        clipboardCopy.select();

        var msg;
        try {
            var successful = document.execCommand(action);
            if(successful)
                msg = action + " successful";
            else
                msg = "Could not perform Clipboard-Action " + action;

            if(clipboardCopy.value.length <= 50)
                msg += " (" + clipboardCopy.value + ")";

            document.fExt.popup(msg);
        }
        catch (err) {
            document.fExt.popup("Error on Clipboard-Action " + action +": " + err);
        }

        if(clipboardCopy)
            clipboardCopy.remove();
    };

    fExt.message = function(msg) {
        if(msg !== undefined && msg.length > 0){
            fExt.fExtMessageText.text(msg);

            if(!fExt.fExtMessage.is(":visible"))
                fExt.show(fExt.fExtMessage);

            fExt.center($("div#fExtMessage"));
        }
        else {
            fExt.hide(fExt.fExtMessage);
            setTimeout(function(){
                fExt.fExtMessageText.text('');
            },fExt.settings.animationSpeed);
        }
    };

    fExt.rotate = function(element, rotation) {
        var jqEl = $(element);
        var degree = jqEl.data('rotation');

        if (!degree)
            degree = 0;

        degree += rotation;

        jqEl.css('-webkit-transform','rotate(' + degree + 'deg)');
        jqEl.data('rotation',degree);
    };

    fExt.zoom = function(element, zoom){
        var jqEl = $(element);
        var zValue = parseFloat(jqEl.css('zoom'));
        var zAdd = parseFloat(zoom) / 100;

        if(!zValue)
            zValue = 1.0;

        var zNew = zValue + zAdd;
        jqEl.css('zoom', zNew);
    };

    fExt.zoomIn = function(element, zoom){
        fExt.zoom(element, zoom);
    };

    fExt.zoomOut = function(element, zoom){
        fExt.zoom(element, zoom * -1);
    };

    switch(fExt.settings.animationType) {
        case "fade":
            fExt.show = function(element, speed) {
                if(speed === undefined)
                    speed = fExt.settings.animationSpeed;
                $(element).fadeIn(speed);
            };
            fExt.hide = function(element, speed) {
                if(speed === undefined)
                    speed = fExt.settings.animationSpeed;
                $(element).fadeOut(speed);
            };
            break;
        case "slide":
            fExt.show = function(element, speed) {
                if(speed === undefined)
                    speed = fExt.settings.animationSpeed;
                $(element).slideDown(speed);
            };
            fExt.hide = function(element, speed) {
                if(speed === undefined)
                    speed = fExt.settings.animationSpeed;
                $(element).slideUp(speed);
            };
            break;
            //case "none":
        default:
            fExt.show = function(element, speed) {
                if(speed === undefined)
                    speed = fExt.settings.animationSpeed;
                $(element).show(speed);
            };
            fExt.hide = function(element, speed) {
                if(speed === undefined)
                    speed = fExt.settings.animationSpeed;
                $(element).hide(speed);
            };
            break;
    }

    switch(fExt.settings.position) {
        case "TopRight":
            fExt.fExtPopup.attr("style", fExt.fExtPopup.attr("style") + "top:50px; right:50px;");
            break;
        case "TopLeft":
            fExt.fExtPopup.attr("style", fExt.fExtPopup.attr("style") + "top:50px; left:50px;");
            break;
        case "BottomRight":
            fExt.fExtPopup.attr("style", fExt.fExtPopup.attr("style") + "bottom:50px; right:50px;");
            break;
        case "BottomLeft":
            fExt.fExtPopup.attr("style", fExt.fExtPopup.attr("style") + "bottom:50px; left:50px;");
            break;
        default:
            break;
    }

    var siteFavIcon = $("link[rel='icon']");
    if(siteFavIcon === undefined) {
        $("head").append('<link id="favIcon" rel="icon" href="');
        siteFavIcon = $("link[rel='icon']");
    }
    else {
        siteFavIcon.attr("id","favIcon");
    }
    var siteFavIconHref = siteFavIcon.attr("href");

    fExt.getSelection = function() {
        return window.getSelection().toString();
    };

    fExt.getSource = function(element) {
        var retVal;

        var jqTarget = $(element);
        var ucTagName = element.tagName.toUpperCase();

        switch(ucTagName) {
            case "IMG":
                retVal = element.src;
                break;
            case "A":
                retVal = element.href;
                break;
            case "VIDEO":
                var videoSource = jqTarget.find("source");
                retVal = (videoSource !== undefined) ? videoSource.get(0).src : undefined;
                break;
            case "INPUT":
                return jqTarget.val();
            default:
                jqTarget = $(element).closest("a");
                if(jqTarget.length > 0)
                    retVal = jqTarget.attr("href");
                break;
        }

        if(retVal) {
            if(!retVal.match("http.*")) {
                retVal = window.location.origin + retVal;
            }
        }
        else {
            retVal = $(element).text();
        }

        return retVal;
    };

    fExt.createStyle(".fExtLoader { background:url('" + loader50 + "')  #EFF7FF no-repeat top center; }");

    // ContextMenu
    fExt.ctxMenu = [];

    fExt.createStyle("#fExtContextMenu,#fExtContextMenu * { text-align: left !important; text-decoration: none !important; z-index: 2147483647 !important; color: #fff; }");
    fExt.createStyle("#fExtContextMenu { position: fixed;}");
    fExt.createStyle("#fExtContextMenu,.ctxSubList { font-size: 14px; background-color: #263238; width: 300px; height: auto; padding: 0;}");
    fExt.createStyle("#fExtContextMenu a,#fExtContextMenu hr,#fExtContextMenu li { width: 100%;}");
    fExt.createStyle("#fExtContextMenu .ctxElement { float: left; clear: left;}");
    fExt.createStyle("#fExtContextMenu li>a,#fExtContextMenu li>div,.ctxSubList li>a,.ctxSubList li>div { padding: 8px;}");
    fExt.createStyle("#fExtContextMenu li hr { margin: 0; border-style: solid; border-color: #666; border-width: 1px 0 0 0;}");
    fExt.createStyle("#fExtContextMenu li.ctxSub { cursor: default; font-weight: bold;}");
    fExt.createStyle("#fExtContextMenu li.ctxSub * { cursor: pointer; font-weight: normal;}");
    fExt.createStyle("#fExtContextMenu li { list-style-type: none; margin: 0 !important;}");
    fExt.createStyle("#fExtContextMenu li.ctxSub:hover>div.ctxSubLabel,");
    fExt.createStyle("#fExtContextMenu li.ctxSub:hover>div.ctxArrow {}");
    fExt.createStyle("#fExtContextMenu li.ctxItem { padding-left: 5px;}");
    fExt.createStyle("#fExtContextMenu li.ctxItem { border: none; position: relative; box-sizing: border-box; transition: all 250ms ease; }");
    fExt.createStyle("#fExtContextMenu li.ctxItem:hover { background: #3a7999; color: #3a7999; box-shadow: inset 0 0 0 3px #3a7999; }");
    fExt.createStyle("#fExtContextMenu li.ctxSub.disabled div.ctxSubLabel:hover,");
    fExt.createStyle("#fExtContextMenu li.ctxItem.disabled:hover a,#fExtContextMenu li.disabled div.ctxSubLabel,#fExtContextMenu li.disabled a { opacity: 0.70; font-style: italic; padding-left: 7px !important;}");
    fExt.createStyle("#fExtContextMenu li.ctxSeparator { display: inline-block;}");
    fExt.createStyle("#fExtContextMenu li.ctxSub div.ctxSubLabel { padding-left: 10px !important; float: left; clear: left;}");
    fExt.createStyle("#fExtContextMenu li.ctxSub div.ctxArrow { float: right; clear: right;}");
    fExt.createStyle("#fExtContextMenu li.ctxSub ul.ctxSubList { position: absolute; height: auto;}");

    fExt.ctxMenu.uniqueID = 1;
    fExt.ctxMenu.allItems = [];
    fExt.ctxMenu.actor = undefined;
    fExt.ctxMenu.html = $('<ul id="fExtContextMenu" class="ctxElement" style="display: none;"></ul>');
    fExt.ctxMenu.html.appendTo("body");

    ctxCtor = function(ret) {
        ret.ItemText = function(value){
            if(value === undefined)
                return this.item.text();

            this.item.text(value);
        };
        ret.Attribute = function(attribute, value){
            if(value === undefined)
                return this.item.attr(attribute);
            else
                this.item.attr(attribute, value);
        };
        ret.Toggle = function(enabled){
            if(enabled === true || (enabled === undefined && this.hasClass("disabled")))
                this.removeClass("disabled");
            else
                this.addClass("disabled");
        };
        ret.IsDisabled = function(){
            return this.hasClass("disabled");
        };
        ret.ID = function() {
            return this.data("Item-ID");
        };
    };

    fExt.ctxMenu.addItem = function(label, action, sub){
        var ret = $('<li class="ctxItem ctxElement"></li>');
        var retObj = $('<a href="#" class="ctxElement"></a>');
        retObj.appendTo(ret);
        ret.item = retObj;

        if(sub === undefined)
            ret.appendTo(fExt.ctxMenu.html);
        else
            ret.appendTo(sub.find("ul:first"));

        ctxCtor(ret);

        ret.Action = undefined;
        ret.Attribute("action", action);
        ret.ItemText(label);
        fExt.ctxMenu.assignID(ret);
        return ret;
    };

    fExt.ctxMenu.addSub = function(label, orientation, sub){
        var ret = $('<li class="ctxElement ctxSub"><div class="ctxElement ctxArrow">&gt;</div><ul class="ctxElement ctxSubList" style="display: none"></ul></li>');
        var item = $('<div class="ctxElement ctxSubLabel">' + label + ' </div>');
        item.appendTo(ret);

        ctxCtor(ret);

        var parentMenu = fExt.ctxMenu.html;
        if(sub !== undefined)
            parentMenu = $(sub).find("ul:first");
        else
            parentMenu = fExt.ctxMenu.html;

        if(orientation === 'bottom' || orientation === undefined || $(parentMenu).children().length === 0) {
            ret.appendTo(parentMenu);
        }
        else {
            ret.insertBefore($(parentMenu).children("li:first"));
        }

        return ret;
    };

    fExt.ctxMenu.addSeparator = function(sub){
        var ret = $('<li class="ctxElement ctxSeparator"><hr/></li>');

        if(sub === undefined)
            ret.appendTo(fExt.ctxMenu.html);
        else
            ret.appendTo(sub.find("ul:first"));

        return ret;
    };

    fExt.ctxMenu.assignID = function(item){
        if(item.ID() !== undefined)
            console.log("Item already has an ID: " + item.ID());

        item.data("Item-ID", fExt.ctxMenu.uniqueID);
        fExt.ctxMenu.uniqueID++;
        fExt.ctxMenu.allItems.push(item);
    };

    fExt.ctxMenu.getItem = function(id) {
        if(id !== undefined) {
            for(i = 0;i < fExt.ctxMenu.allItems.length; i++){
                var item = fExt.ctxMenu.allItems[i];
                if(id === item.ID())
                    return item;
            }
        }

        return undefined;
    };

    $("#fExtContextMenu").on("click", "li", function( event ) {
        var sender = fExt.ctxMenu.getItem($(this).data("Item-ID"));
        if(sender === undefined || sender.Action === undefined)
            return true;

        event.preventDefault();
        if(!sender.IsDisabled())
            sender.Action(event, sender, fExt.ctxMenu.actor);

        fExt.hide(fExt.ctxMenu.html);
        return false;
    });

    $("#fExtContextMenu").on("mouseenter", "li.ctxSub", function(event){
        var sub = $(this);
        var subOffs = sub.offset();
        var ul = sub.children("ul.ctxSubList:first");

        if(ul.children("li.ctxItem").length === 0)
            return;

        fExt.show(ul, 0);

        var height = ul.height();
        var width = ul.width();
        var y = subOffs.top;
        var x = subOffs.left + sub.width();
        if ((x + width) >= window.screen.availWidth)
            x = subOffs.left - ul.width();
        if ((y + height + 100 - window.scrollY) >= window.screen.availHeight)
            y = y - height;

        if ((y - window.scrollY) < 0)
            y = window.scrollY;
        if ((x  - window.scrollX) < 0)
            x = window.scrollX;

        ul.offset({ top: y, left: x});
    });
    $("#fExtContextMenu").on("mouseleave", "li.ctxSub", function(event){
        var ul = $(this).children("ul.ctxSubList:first");
        fExt.hide(ul);
    });
    $("#fExtContextMenu").on("mouseenter", "li.ctxSub", function(event){
        var ul = $(this).children("ul.ctxSubList:first");
        ul.stop();
        if($(this).find("li.ctxItem").length > 0)
            ul.show(0);
    });

    fExt.customContextMenuHandler = function (event){
        event.preventDefault();

        fExt.ctxMenu.actor = $(event.target);

        var y = event.clientY - fExt.settings.toleranceY,
            x = event.clientX - fExt.settings.toleranceX;
        if ((x + fExt.ctxMenu.html.width()) >= window.screen.availWidth)
            x = x - fExt.ctxMenu.html.width() + (fExt.settings.toleranceX * 1.5);
        if ((y + fExt.ctxMenu.html.height() + 100) >= window.screen.availHeight)
            y = y - fExt.ctxMenu.html.height() + (fExt.settings.toleranceY * 1.5);

        if (y < 0)
            y = 0;
        if (x < 0)
            x = 0;

        fExt.ctxMenu.html.css({
            top: y,
            left: x
        });

        $("#fExtContextMenu").trigger("fExtContextMenuOpening", [fExt.ctxMenu.actor]);

        fExt.show(fExt.ctxMenu.html, 0);

        return false;
    };

    $("#fExtContextMenu").on("fExtContextMenuOpening", function(event, actor){
    });

    if(fExt.settings.hideContextMenuOnLeave) {
        $("#fExtContextMenu").mouseleave(function(){
            fExt.hide(fExt.ctxMenu.html);
        });
        $("#fExtContextMenu").mouseenter(function(){
            fExt.ctxMenu.html.stop();
            fExt.ctxMenu.html.show(0);
        });
    }
    else {
        $("body").click(function(e){
            if(!$(this).hasClass("ctxElement") && fExt.ctxMenu.html.is(":visible"))
                fExt.hide(fExt.ctxMenu.html,0);
        });
    }

    $(document).on('contextmenu', function(event){
        if(fExt.settings.customContextMenu) {
            if(!$(event.target).hasClass("ctxElement"))
                fExt.customContextMenuHandler(event);
            else
                fExt.ctxMenu.html.hide();
        }
    });

    $(document).on('keyup', function(event){
        if((event.which === 17 && event.altKey) || (event.which === 18 && event.ctrlKey)) {
            fExt.settings.customContextMenu = !fExt.settings.customContextMenu;
            fExt.popup("Context-Menu has been toggled " + (fExt.settings.customContextMenu ? "on" : "off"));
        }
    });

    // Finalize
    document.fExt = fExt;

    fExt.fExtPopup.click(function(e){
        fExt.popupQueue = [];
        fExt.hide($(this));
    });

    fExt.fExtMessage.click(function(e){
        fExt.hide($(this));
    });

    console.log("frisch's UserScript Extender initialized!");
}