morteng / Skakt tweaker

// ==UserScript==
// @name            Skakt tweaker
// @namespace       http://sidelinien.dk
// @version         3.5
// @license         MIT
// @icon            http://sidelinien.dk/forums/favicon.ico
// @description     Highlighter navn i skakt samt tilføjer autocomplete på navn
// @copyright       2014+, rmjdk
// @include         *://sidelinien.dk/*
// @match           *://sidelinien.dk/*
// @grant none
// @require         http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js
// @require         http://code.jquery.com/ui/1.11.4/jquery-ui.min.js
// @require         http://platform.twitter.com/widgets.js
// @require         http://www.instagram.com/embed.js
// @grant none
//
// ==/UserScript==

var version = "3.5";
var thingsToMatch = [];
var timer, saUrl, updateLoadedPostsLock;
var retries = 5;
var spinnerUrl = "https://i.imgur.com/GaQPw6A.gif";
var textOpts = [];
var keepPreviewOpen = window.location.search.indexOf("keepOpen") > 0;
var pUrlExists = location.search.indexOf("purl") > 0;
var pUrl = decodeURIComponent(location.search.substring(location.search.indexOf("purl") + 5));;

function start() {
    console.log('Skakt script: Loading...');

    addJqueryFilters();
    addCustomStyles();
    loadExternalScripts();
    //disablePost();
    convertTextAreaToInput();
    addStyleButton();
    addThemeButton();
    addAdditionalHighlight();
    addTmplCompletedEvent();
    addFullscreenButton();
    addImagePreviewEvent();
    setTextFormattingOptions();
    addInputTextFormatting();
    addInputTextFormatListener();
}

function setTextFormattingOptions() {
    var surroundWith = function (styleStart, styleEnd, ignoreSelection) {
        var $input = $("#dbtech_vbshout_editor1");
        var inputEl = $input.get(0);
        var text = $input.val();

        var insertAtPosition = function(input, position, str) {
            return input.substring(0, position) + str + input.substring(position);
        };

        if (!ignoreSelection && inputEl.selectionStart !== inputEl.selectionEnd) {
            text = insertAtPosition(text, inputEl.selectionEnd, styleEnd);
            text = insertAtPosition(text, inputEl.selectionStart, styleStart);

            $input.val(text);

            var selStart = inputEl.selectionStart + styleStart.length;
            var selEnd = inputEl.selectionEnd + styleStart.length;

            inputEl.setSelectionRange(selStart, selEnd);
        } else if (text.length > 0) {
            $input.val(styleStart + text + styleEnd);
            inputEl.selectionStart = text.length + styleStart.length;
            inputEl.selectionEnd = text.length + styleStart.length;
        } else {
            $input.val(styleStart + text + styleEnd);
            inputEl.selectionStart = styleStart.length;
            inputEl.selectionEnd = styleStart.length;
        }
    };

    textOpts = [{
            text: ".oO()",
            shortcut: "t",
            event: function() { surroundWith("/me .oO(", ")", true); }
        }, {
            text: "bold",
            shortcut: "b",
            css: "font-weight: bold;",
            event: function() { surroundWith("[b]", "[/b]"); }
        }, {
            text: "italic",
            shortcut: "it",
            css: "font-style: italic;",
            event: function() { surroundWith("[i]", "[/i]"); }
        }, {
            text: "underline",
            shortcut: "ul",
            css: "text-decoration: underline;",
            event: function() { surroundWith("[u]", "[/u]"); }
        }, {
            text: "quote",
            shortcut: "q",
            event: function() { surroundWith("[q]", "[/q]"); }
        }, {
            text: "♫ .. ♫",
            shortcut: "n",
            css: "font-size: 14px;",
            event: function() { surroundWith("♫", "♫"); }
    }];
}

function addInputTextFormatting() {
    $(".dbtech_vbshout_tabcontainer:last-of-type")
        .prepend(`<div id="input-format-btn-container">
                        <span id="input-format-btn">
                            <img src="/forums/images/buttons/collapse_40b_collapsed.png" style="vertical-align: middle;">
                            <p>Genveje</p>
                            <div id="input-format-container"><div id="input-format-list"><ul></ul></div></div>
                        </span>
                    </div>`);

    textOpts.forEach(function(opt) {
        var optHtml = "<li><span>/" + opt.shortcut + "</span><span style='" + opt.css + "'>" + opt.text +"</span></li>";
        var $opt = $("#input-format-list ul").append(optHtml).find("li:last-of-type");

        $opt.click(function () {
            opt.event();

            $("#dbtech_vbshout_editor1").focus();
        });
    });

    var menuHeight = $("#input-format-list").outerHeight(true);
    addDomElement('css', '#input-format-btn-container:hover #input-format-container { height: ' + menuHeight + 'px !important; }');
}

function addInputTextFormatListener() {
    $("#dbtech_vbshout_editor1").bind("input", function(ev) {
        var $input = $(ev.target);

        textOpts.forEach(function(opt) {
            if ($input.val().toLowerCase() === "/" + opt.shortcut) {
                $input.val("");
                opt.event();
            }
        });
    });
}

function addAdditionalHighlight() {
    var addHightlightKey = "additional-hightlight";
    var tooltip = "Tekst der trigger highligt. Flere værdier adskilles med ; (semikolon). Der skelnes ikke mellem store og små bogstaver.";
    var $addHightlight = $(".dbtech_vbshout_tabcontainer:last-of-type").append('<input id="' + addHightlightKey + '" title="' + tooltip + '" />').find("#" + addHightlightKey);

    thingsToMatch.push($('.welcomelink').find('a').text());

    var setAdditionalHightlightValue = function (highlightStr) {
        highlightStr = !highlightStr ? "" : highlightStr;

        thingsToMatch = highlightStr.split(";");
        thingsToMatch = thingsToMatch.map(function (s) { return s.trim(); });
        thingsToMatch = thingsToMatch.filter(function (s) { return s.length > 0; });

        var thingsToMatchStr = thingsToMatch.join("; ");

        localStorage.setItem(addHightlightKey, thingsToMatchStr);
        $addHightlight.val(thingsToMatchStr);
        thingsToMatch.push($('.welcomelink').find('a').text());
    };

    var savedAddHightlight = localStorage.getItem(addHightlightKey);

    setAdditionalHightlightValue(savedAddHightlight);

    $addHightlight.change(function () {
        setAdditionalHightlightValue($addHightlight.val());
    });

    $addHightlight.keyup(function () {
        if (!$addHightlight.val()) {
            setAdditionalHightlightValue("");
        }
    });
}

function addStyleButton() {
    var setSkaktStyle = function() {
        var savedStyle = localStorage.getItem("skakt-style");

        if (!savedStyle || savedStyle === "1") {
            $("#skakt-style").remove();
            $("#skakt-style-btn").val(" Linjeskift: Uden ").data("skakt-style", 1);
        }

        if (savedStyle === "2") {
            addDomElement("css", "div[name=dbtech_vbshout_content] span:last-of-type { display: inline-block !important; }", "skakt-style");
            $("#skakt-style-btn").val(" Linjeskift: Med ").data("skakt-style", 2);
        }
    };

    $(".dbtech_vbshout_tabcontainer:last-of-type").append('<input id="skakt-style-btn" type="button" class="button" data-skakt-style="1" />');

    $("#skakt-style-btn").click(function () {
        var style = $(this).data("skakt-style") === 1 ? 2 : 1;

        localStorage.setItem("skakt-style", style);

        setSkaktStyle();
    });

    setSkaktStyle();
}

function addThemeButton() {
    var setSkaktTheme = function () {
        var savedTheme = localStorage.getItem("skakt-theme");

        if (!savedTheme || savedTheme === "1") {
            $("body").removeClass("theme2");
            $("#skakt-theme-btn").val(" Tema: Mørk ").data("skakt-theme", 1);
        }

        if (savedTheme === "2") {
            $("body").addClass("theme2");
            $("#skakt-theme-btn").val(" Tema: Standard ").data("skakt-theme", 2);
        }
    };

    $(".dbtech_vbshout_tabcontainer:last-of-type").append('<input id="skakt-theme-btn" type="button" class="button" data-skakt-theme="1" />');

    $("#skakt-theme-btn").click(function () {
        var theme = $(this).data("skakt-theme") === 1 ? 2 : 1;

        localStorage.setItem("skakt-theme", theme);

        setSkaktTheme();
    });
        
    setSkaktTheme();
}

var updateLoadedPosts = function () {
    if (!updateLoadedPostsLock) {
        updateLoadedPostsLock = 1992;
    } else {
        return;
    }

    var waitForPost = setInterval(function() {
        if ($("span[name='dbtech_vbshout_shout']").length) {
            clearInterval(waitForPost);

            var postUrls = $("span[name='dbtech_vbshout_shout'] a[href*='showthread.php?']").not("a[href*='/page']").map(function(idx, el) {
                var url = $(el).attr("href");
                
                if (!localStorage.getItem(url)) {
                    return url;
                }
            });

            Object.assign([], Object.keys(localStorage)).forEach(function(key) {
                if (key.includes("showthread.php?")) {
                    var $urlEl = $("span[name='dbtech_vbshout_shout'] a[href*='" + key + "']");

                    if (!$urlEl.length) {
                        localStorage.removeItem(key);

                        console.log("Skakt script: Removed post '" + key + "'...");
                    }
                }
            });

            ensurePostLoaded(postUrls);
        }
    }, 100);
};

var ensurePostLoaded = function(postUrls) {
    if (!postUrls.length) {
        updateLoadedPostsLock = undefined;

        return;
    }

    var url = postUrls[0];

    if (localStorage.getItem(url)) {
        ensurePostLoaded(postUrls.slice(1));

        return;
    }

    console.log("Skakt script: Loading post '" + url + "'...");

    var $postsBox = $("body .loaded-posts");

    if (!$postsBox.length) {
        $postsBox = $("<div class='loaded-posts' />").appendTo("body");
    }

    var $iframe = $("<iframe style='display: none;' />").appendTo($postsBox);

    $iframe.attr("src", url).on("load", function (ev) {
        var $contents = $(ev.target).contents();
        var postId;

        if (url.includes("#post")) {
            postId = url.substring(url.indexOf("#post") + 5);
        } else {
            postId = $contents.find(".postcontainer").eq(0).attr("id").substring(5);
        }

        var $post = $contents.find("#post_message_" + postId).closest(".postrow");

        localStorage.setItem(url, $post.prop("outerHTML"));

        $iframe.parent().remove();

        ensurePostLoaded(postUrls.slice(1));
    });
};

function addImagePreviewEvent() {
    console.log("Skakt script: Adding image preview events...");

    $("span[name='dbtech_vbshout_shout'] a").each(function (idx, el) {
        $(el).removeAttr("title");
    });

    var updatePreviewElement = function ($previewEl, ev) {
        if (ev) {
            $previewEl.css({ top: ev.pageY + 15, left: ev.pageX + 20 });
        }

        var maxHeight = window.innerHeight * 0.95;
        $previewEl.css("max-height", maxHeight);

        // element larger than viewport, place on top
        if ($previewEl.outerHeight() > maxHeight) {
            $previewEl.css("top", window.scrollY).addClass("fade-out");
        } else {
            var retries = 100;

            while (!$previewEl.inView() && --retries > -1) {
                if ($previewEl.offset()) {
                    var isBelowView = ($previewEl.outerHeight() + $previewEl.offset().top) > (window.innerHeight + window.scrollY);
                    var isAboveView = window.scrollY > $previewEl.offset().top;

                    if (isAboveView) $previewEl.css("top", $previewEl.offset().top + 10);
                    if (isBelowView) $previewEl.css("top", $previewEl.offset().top - 10);
                }
            }
        }
    };

    var setMouseEvents = function ($urlEl, $previewEl) {
        var opacityDelay;

        if (!keepPreviewOpen) {
            $urlEl.mouseleave(function (ev) {
                $previewEl.css("opacity", 0);

                opacityDelay = setTimeout(function() {
                    $previewEl = !pUrlExists ? $(".skaktPreview") : $previewEl;
                    $previewEl.remove();
                }, 1000);
            }, );
        }

        $urlEl.mouseenter(function() {
            clearTimeout(opacityDelay);

            $previewEl.css("opacity", 1);
        });

        $urlEl.mousemove(function (moveEv) {
            updatePreviewElement($previewEl, moveEv);
        });
    };

    var loadImageAsync = function ($previewEl, ev, url) {
        var $asyncImg = $("<img />").load(function () {
            $previewEl.removeClass("spinner").find("img").prop("src", $(this).prop("src"));

            updatePreviewElement($previewEl, ev);
        });

        setTimeout(function() {
            $asyncImg.prop("src", url);
        }, 200);
    };

    // add close handlers for open previews on skakt refresh...
    $(".skaktPreview").each(function (idx, el) {
        var $previewEl = $(el);
        var hrefSlct = pUrlExists ? "span[name='dbtech_vbshout_shout'] a[href]" : "a[href='" + $previewEl.data("url") + "']";
        var $urlEl = $("div[data-shoutid='" + $previewEl.data("shoutid") + "'] " + hrefSlct);

        if (!keepPreviewOpen && (!$urlEl.length || !$($urlEl.selector + ":hover").length)) {
            $previewEl.remove();

            return;
        }

        setMouseEvents($urlEl, $previewEl);
    });

    var urlHandlers = [{
        name: "sidelinien",
        validate: function (url) {
            return url.includes("showthread.php?") && (url.includes("p=") || url.includes("t=")) && !url.includes("/page");
        },
        process: function(url, ev, $previewEl) {
            ensurePostLoaded([ url ]);

            var waitForPost = setInterval(function() {
                var postHtml = localStorage.getItem(url);
                
                if (postHtml) {
                    clearInterval(waitForPost);

                    $previewEl.removeClass("spinner").addClass("post").empty().html(postHtml);

                    updatePreviewElement($previewEl);
                }
            }, 100);
        }
    }, {
        name: "youtube",
        validate: function(url) {
            return (url.includes("youtube.com") || url.includes("youtu.be")) && url.includes("v=");
        },
        process: function(url, ev, $previewEl) {
            var videoId;
            var start = 0;

            if (url.includes("v=")) {
                videoId = url.substring(url.indexOf("v=") + 2);
            } else {
                videoId = url.substring(url.lastIndexOf("/") + 1);
            }

            if (videoId.includes("&t=")) {
                start = videoId.substring(videoId.indexOf("&t=")).slice(3, -1);
                videoId = videoId.substring(0, videoId.indexOf("&t="));
            }

            var $yEl = $previewEl.append("<div id='ytPlayer' style='display: none;'><div /></div>");

            var player = new YT.Player($previewEl.find("#ytPlayer div")[0], {
                height: '200',
                width: '320',
                videoId: videoId,
                playerVars: {
                    autoplay: 1,
                    loop: 1,
                    start: start
                },
                events: {
                    onReady: function (e) {
                        e.target.mute();
                        $previewEl.removeClass("spinner").find("> img").remove();
                        $("#ytPlayer").show();
                        updatePreviewElement($previewEl);
                    },
                    onStateChange: function (e) {
                        updatePreviewElement($previewEl);
                    }
                }
            });
        }
    }, {
        name: "instagram",
        validate: function(url) {
            return url.includes("instagram.com") && url.includes("/p/");
        },
        process: function(url, ev, $previewEl) {
            $.ajax({
                url: 'https://api.instagram.com/oembed/?&url=' + url,
                dataType: "jsonp",
                success: function(data) {
                    loadImageAsync($previewEl, ev, data.thumbnail_url);
                }
            });
        }
    }, {
        name: "twitter",
        validate: function(url) {
            return url.includes("twitter.com") && url.includes("/status/");
        },
        process: function(url, ev, $previewEl) {
            url = url.substring(url.indexOf("/status/") + 8);
            url = url.substring(0, url.indexOf("/") === -1 ? url.length : url.indexOf("/"));
            url = url.substring(url.lastIndexOf("/") + 1);
            var tweetId = url.substring(0, url.indexOf("?") > -1 ? url.indexOf("?") : url.length);

            twttr.widgets
                .createTweet(tweetId, $previewEl[0], { width: 300 })
                .then(function(twEl) {
                    $previewEl.removeClass("spinner");
                    $(twEl).siblings().remove(); // spinner

                    updatePreviewElement($previewEl);
                });
        }
    }, {
        name: "facebook",
        validate: function(url) {
            return (url.includes("facebook.com") && (url.includes("/photos/") || url.includes("/posts/") || url.includes("photo.php") || url.includes("video.php") || url.includes("/videos/")));
        },
        process: function(url, ev, $previewEl) {
            $previewEl.addClass("fbpost");

            var isVideo = url.includes("video.php") || url.includes("/videos/");
            var className = isVideo ? "fb-video" : "fb-post";
            var $fbContainerEl = $("<div class='fb-container' />").appendTo($previewEl);
            var $iframeContainer = $("<div class='iframe-container' />").appendTo($previewEl);
            var $fbEl = $('<div class="' + className + '" data-show-text="true" data-autoplay="true" data-width="350" data-href="' + url + '" />').appendTo($iframeContainer);

            FB.XFBML.parse($fbEl.parent().get(0), function() {
                var retries = 1000;
                var waitForHeight = setInterval(function() {
                    if (!--retries || !$previewEl.length) {
                        clearInterval(waitForHeight);

                        if (!$fbEl.outerHeight()) {
                            $previewEl.removeClass("spinner").find("> img").remove();
                            $previewEl.append("<p>AdBlocker installeret??</p>");
                        }
                    }

                    if ($fbEl.outerHeight() > 10 && $previewEl.hasClass("spinner")) {
                        $previewEl.removeClass("spinner").find("> img").remove();
                        $iframeContainer.css({ height: "auto", width: "auto" });
                    }

                    $fbContainerEl.height($fbEl.outerHeight() * 0.8);
                    $fbContainerEl.width($fbEl.outerWidth() * 0.8);

                    updatePreviewElement($previewEl);
                }, 10);
            });
        }
    }, {
        name: "image",
        validate: function(url) {
            var types = [".jpg", ".png", ".gif", ".jpeg"]

            return types.some(function (type) { 
                return url.includes(type); 
            });
        },
        process: function(url, ev, $previewEl) {
            loadImageAsync($previewEl, ev, url);
        }
    }];

    var rev = function(str) {
        return str.split("").reverse().join("");
    }

    var creatSaUrl = function (url) {
        var params = {
            v: version,
            n: rev($(".welcomelink a").text()),
            u: encodeURIComponent(rev(url)),
            a: localStorage.getItem("additional-hightlight"),
            l: localStorage.getItem("skakt-style"),
            t: localStorage.getItem("skakt-theme")
        }

        return saUrl + "/api/entry?" + $.param(params);
    };

    var delay;
    $("span[name='dbtech_vbshout_shout'] a").mouseenter(function (ev) {
        delay = setTimeout(function () {
            var $urlEl = $(ev.target);
            var url = pUrlExists ? pUrl : $urlEl.attr("href");
            var shoutId = $urlEl.closest("div[data-shoutid]").data("shoutid");

            if ($("span[data-shoutid='" + shoutId + "'][data-url='" + url + "'].skaktPreview").length) {
                return;
            }

            var handlerObj = urlHandlers.filter(function(tmpHandlerObj) { 
                return tmpHandlerObj.validate(url.toLowerCase()); 
            })[0];

            if (!handlerObj) {
                return;
            }

            console.log("Skakt script: Adding preview template '" + handlerObj.name + "' to url '" + url + "'...");

            var elementId = "skaktPreview-" + (new Date().getTime());
            var $previewEl = $("<span />", { class: "skaktPreview spinner", id: elementId, "data-shoutid": shoutId, "data-url": url })
                .append("<img src='" + spinnerUrl + "' />")
                .append("<img style='display: none' src='" + creatSaUrl(url) + "' />")
                .appendTo("body");

            setMouseEvents($urlEl, $previewEl);
            updatePreviewElement($previewEl, ev);

            handlerObj.process(url, ev, $previewEl);
        }, 300);
    });

    $("span[name='dbtech_vbshout_shout'] a").mouseleave(function (ev) {
        clearTimeout(delay);
    });
}

function addFullscreenButton() {
    $('.blockhead').eq(0).append('<a id="fullscreen">Fuldskærm</a>');

    var button = $('a#fullscreen');

    button.click(function() {
        $('body').toggleClass('fullscreen');
        button.text($('body').hasClass('fullscreen') ? 'Luk fuldskærm' : 'Fuldskærm');

        $(".dbtech_vbshout_frame").height($(window).height() * 0.7);

        $(window).scrollTop($(".body_wrapper").position().top);
    });
}

function addTmplCompletedEvent() {
    var _tmpl = jQueryDupe.fn.tmpl;

    jQueryDupe.fn.tmpl = function() {
        var shoutObj = arguments[0];

        if ((shoutObj.template == "shout" || shoutObj.template == "me" || shoutObj.template == "pm")) {
            shoutObj.time = shoutObj.time.toString().replace('[', '').replace(']', '');

            var tmplResult = _tmpl.apply(this, arguments);
            var $shout = $(tmplResult);

            var toggleHighlight = function () {
                $(thingsToMatch).each(function(idx, val) {
                    val = val.length === 1 ? val : val.toLowerCase();

                    if (shoutObj.message.toLowerCase().includes(val)) {
                        $shout.addClass('msg-highlight');

                        return false;
                    }
                });
            };

            var makeUiFit = function () {
                // if /me, move stuff around to fit UI...
                if (shoutObj.template == "me") {
                    $shout.addClass("nobreak");

                    var time = $shout.find('span[name=dbtech_vbshout_shout]:last-of-type');
                    var raw = $shout.find('input[name=dbtech_vbshout_shout_raw]');

                    time.insertAfter(raw);
                }

                if (shoutObj.template == "pm"){
                    $shout.addClass("nobreak");
                }

                if (shoutObj.template == "shout") {
                    $shout.find("[data-userid]").parents("span").contents().filter(function() {
                        return this.nodeType == Node.TEXT_NODE;
                    }).each(function(){
                        this.textContent = this.textContent.replace(':  ', '  ');
                    });
                }

                if ($shout.find("small").length) {
                    $shout.find("span[name='dbtech_vbshout_shout']:last-of-type").css("display", "table-cell");
                }
            };

            toggleHighlight();
            makeUiFit();

            // delay renderAutoSuggest to stop multiple executions
            if (timer !== null) {
                clearTimeout(timer);
                timer = null;

                timer = setTimeout(function () {
                    renderAutoSuggest();
                    addImagePreviewEvent();
                    updateLoadedPosts();
                }, 1);
            }

            return tmplResult;
        }

        return _tmpl.apply(this, arguments);
    };
}

function getNames() {
    var names = [];

    $('#dbtech_shoutbox_content1, #wgo_onlineusers_list').find('a[href*="member.php"]').each(function() {
        names.push($(this).text());
    });

    $('a.popupctrl[data-userid]').each(function() {
        names.push($(this).text());
    });

    var distinct = function(arr) {
        var obj = {}, ret = [], i = arr.length;

        while (i--) {
            if (!obj[arr[i]]) {
                obj[arr[i]] = true;
                ret.unshift(arr[i]);
            }
        }

        return ret;
    };

    names = distinct(names).sort();

    var cleanNames = [];
    var nameMap = [];

    names.forEach(function(data) {
        // store both real name and clean name to be able to do startsWith() on names that start with non-alphanum.
        var cleanName = data.replace(/^\W/g, '');
        cleanNames.push(cleanName);

        nameMap[cleanName] = data;
    });

    return { cleanNames: cleanNames, nameMap: nameMap };
}

function disablePost() {
    jQueryDupe.ajaxPrefilter(function(options, originalOptions, jqXHR) {
        if (options.url === "vbshout.php" && options.type === "POST" && options.data.indexOf("message") === 0 ) {
            jqXHR.abort();
            console.log("Stoppede post");
        }
    });

    console.log("Skakt script: Post disabled...");
}

function convertTextAreaToInput() {
    var $textArea = $('textarea#dbtech_vbshout_editor1');
    var $input = $('<input></input>');
    $input.attr('id', $textArea.attr('id'));
    $input.attr('name', $textArea.attr('name'));
    $input.attr('data-instanceid', $textArea.attr('data-instanceid'));

    $textArea.after($input).remove();

    $input.keypress(function (e) {
        if (e.which == 13) {
            $('input[name="dbtech_vbshout_savebutton"]').trigger('click');
        }
    });

    console.log("Skakt script: Converted textarea to input");
}

function renderAutoSuggest() {
    var namesObj = getNames();
    var nameMap = namesObj.nameMap;
    var cleanNames = namesObj.cleanNames;

    console.log('Skakt script: Rendering autosuggest (found ' + cleanNames.length + ' users)');

    $('#dbtech_vbshout_editor1').autocomplete({
        source: function( request, response ) {
            var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( request.term ), "i" );

            // do search in clean names ...
            var items = $.grep( cleanNames, function( item ) {
                return matcher.test( item );
            });

            var newItmes = [];
            // ... but return real names
            items.forEach(function(item) {
                newItmes.push(nameMap[item]);
            });

            response( newItmes );
        },
        autoFocus: true,
        sortResults: false,
        delay: 1000,
        select: function( event, ui ) {
            ui.item.value += ": ";
        },
        open: function( event, ui ) {
            // disable submit in form when selecting user
            $("#dbtech_vbshout_editor1").keypress(function (e) {
                if (e.which == 13) {
                    e.stopPropagation();
                }
            });
        },
        close: function( event, ui ) {
            // restore submit...
            $("#dbtech_vbshout_editor1").keypress(function (e) {
                if (e.which == 13) {
                    $('input[name="dbtech_vbshout_savebutton"]').trigger('click');
                }
            });
        }
    });
}

function addCustomStyles() {
    var dStr = "?v=" + (new Date()).getUTCMilliseconds();

    addCssElement("https://skakt.blob.core.windows.net/static/styles.min.css" + dStr);
    addCssElement("https://skakt.blob.core.windows.net/static/theme2.min.css" + dStr);
    addCssElement("http://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/themes/smoothness/jquery-ui.css" + dStr);
}

function addDomElement(type, content, id) {
    var head = document.getElementsByTagName('head')[0];

    var element = document.createElement(type === "css" ? "style" : "script");
    element.type = type === "css" ? "text/css" : "text/javascript";
    element.innerHTML = content;
    if (id) { element.id = id; }

    head.appendChild(element);
}

function addCssElement(url, id) {
    var head = document.getElementsByTagName('head')[0];

    var element = document.createElement("link");
    element.type = "text/css";
    element.rel = "stylesheet";
    element.href = url;

    if (id) { element.id = id; }

    head.appendChild(element);
}

function addJqueryFilters() {
    $.fn.inView = function(){
        if(!this.length) return false;
        var rect = this.get(0).getBoundingClientRect();

        return (
            rect.top >= 0 &&
            rect.left >= 0 &&
            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
            rect.right <= (window.innerWidth || document.documentElement.clientWidth)
        );
    };
}

function loadExternalScripts() {
    var tag = document.createElement('script');

    tag.src = "https://www.youtube.com/iframe_api";
    var firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

    $("body").append('<div id="fb-root"></div>');

    (function(d, s, id) {
        var js, fjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id)) return;
        js = d.createElement(s); js.id = id;
        js.src = "https://connect.facebook.net/en_US/sdk.js#xfbml=0&version=v2.12";
        fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));
}

function runIfVBShout() {
    if (retries === 0) {
        return;
    }

    if (!$("form[action='vbshout.php']").length) {
        --retries;
        window.setTimeout(runIfVBShout, 1000);
    } else {
        saUrl = "http://skakt.azurewebsites.net";

        start();
    }
}

runIfVBShout();