morteng / Skakt tweaker

// ==UserScript==
// @name            Skakt tweaker
// @namespace       http://sidelinien.dk
// @version         3.6.0
// @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/*
// @updateURL       https://openuserjs.org/meta/morteng/Skakt_tweaker.meta.js
// @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.6.0";
var thingsToMatch = [];
var saUrl, updateLoadedPostsLock, shoutIds;
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));
var docTitle = document.title;

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

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

    function addChangeRefresh() {
        Object.keys(vBShout.instanceOptions).forEach(function (key) {
            console.log('Skakt script: Setting refresh delay on instance ' + key + '...');

            vBShout.instanceOptions[key]["refresh"] = 3;
        });
    }

    function setEkstrabladetLinkName() {
        $("span[name='dbtech_vbshout_shout'] a").each(function (idx, el) {
            var url = $(el).attr("href");

            if (url.indexOf("ekstrabladet.dk") > -1 && url.split("/").length == 7) {
                var urlParts = url.split("/");
                var articleCategory = urlParts[3] + "/" + urlParts[4];
                var articleTitle = urlParts[5].replaceAll("-", " ");

                $(el).text("ekstrabladet.dk (" + articleCategory + ") - " + articleTitle);
            }
        });
    }

    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;
            }
        };

        var insertText = function (text) {
            var $input = $("#dbtech_vbshout_editor1");
            var curText = $input.val();

            $input.val(curText + " " + text);
        }

        textOpts = [{
            text: ".oO()",
            shortcut: "t",
            event: function () { surroundWith("/me .oO(", ")", true); }
        }, {
            text: "¯\_(ツ)_/¯",
            shortcut: "dc",
            event: function () { insertText("¯\_(ツ)_/¯"); }
        }, {
            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 setMenuHeight = function () {
            var menuHeight = $("#input-format-list").outerHeight(true);

            addDomElement('css', '#input-format-btn-container:hover #input-format-container { height: ' + menuHeight + 'px !important; }');
        };

        setMenuHeight();

        setTimeout(setMenuHeight, 2000);
    }

    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 ensureNewPosts = function () {
            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;
                }
            });

            ensurePostLoaded(postUrls);
        }

        var removeOldPosts = function () {
            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 + "'...");
                    }
                }
            });
        }

        ensureNewPosts();
        removeOldPosts();
    };

    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);

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

                    return;
                }
            }, 500);

            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("v=")) || url.includes("youtu.be");
            },
            process: function (url, ev, $previewEl) {
                var parsedUrl = new URL(url);
                var videoId;
                var start = parsedUrl.searchParams.get("t") || 0;

                if (parsedUrl.searchParams.has("v")) {
                    videoId = parsedUrl.searchParams.get("v");
                } else {
                    videoId = parsedUrl.pathname.substring(1);
                }

                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) {
                window.__igEmbedLoaded = window.__igEmbedLoaded || function (embedData) {
                    var $iframe = $("#" + embedData.frameId);
                    var $previewEl = $iframe.parents(".skaktPreview");
                    var $igContainer = $("<div class='ig-container' />").appendTo($previewEl);
                    var $iframeContainer = $previewEl.find(".iframe-container");

                    $previewEl.removeClass("spinner").find("> img").remove();

                    $iframeContainer.css({ height: "auto", width: "auto" });
                    $igContainer.height($iframeContainer.outerHeight() * 0.8);
                    $igContainer.width($iframeContainer.outerWidth() * 0.8);

                    updatePreviewElement($previewEl);
                };

                $.ajax({
                    type: "GET",
                    url: 'https://api.instagram.com/oembed/?&' + $.param({ url: url, omitscript: true }),
                    success: function (data) {
                        $("<div class='iframe-container' />").html(data.html).appendTo($previewEl);

                        instgrm.Embeds.process();
                    }
                });
            }
        }, {
            name: "twitter",
            validate: function (url) {
                return (url.includes("twitter.com") || url.includes("x.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/") || url.includes("/watch/")));
        },
        process: function (url, ev, $previewEl) {
            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", "format=jpg", "format=png"];

                return types.some(function (type) {
                    return url.includes(type);
                });
            },
            process: function (url, ev, $previewEl) {
                loadImageAsync($previewEl, ev, url);
            }
        }, {
            name: "bluesky",
            validate: function (url) {
                return (url.includes("https://bsky.app/") && (url.includes("/post/")));
            },
            process: function (url, ev, $previewEl) {
                var corsUrl = "http://api.allorigins.win/get?url=";
                corsUrl += "https://embed.bsky.app/oembed?" + $.param({ url: url });

                $.getJSON(corsUrl, function (data) {
                    var jsonObj = JSON.parse(data.contents);

                    var $bskyContainer = $("<div class='bsky-container' />")
                        .css({ position: "relative", width: "300px", overflow: "hidden" });

                    var $iframeContainer = $("<div class='iframe-container' />")
                        .css({ "transform": "scale(1)" });

                    $iframeContainer
                        .hide()
                        .html(jsonObj.html)
                        .appendTo($bskyContainer);

                    $bskyContainer.appendTo($previewEl);

                    var waitForBlueskyEmbed = setInterval(function () {
                        var $iframe = $iframeContainer.find("iframe[data-bluesky-id]");

                        if ($iframe.length) {
                            clearInterval(waitForBlueskyEmbed);

                            $iframe.load(() => {                                
                                $iframeContainer
                                    .show()
                                    .css({ height: "auto", width: "auto" })
                                    .find("div.bluesky-embed").css("margin", "0");

                                $previewEl.removeClass("spinner").find("> img").remove();

                                var retries = 25;
                                var waitForSizeRendered = setInterval(() => {
                                    if (!--retries) {
                                        clearInterval(waitForSizeRendered);
                                    }

                                    $bskyContainer.height($iframeContainer.outerHeight());
                                    $bskyContainer.width($iframeContainer.outerWidth());

                                    updatePreviewElement($previewEl);
                                }, 100);
                            });
                        }

                        updatePreviewElement($previewEl);
                    }, 100);
                });
            }
        }];

        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 updatePostsNotification() {
        if (document.hidden) {
            if (!shoutIds) {
                shoutIds = $(".dbtech_vbshout_shout").map(function (i, el) { return $(el).data("shoutid"); });

                return;
            }

            var currentShoutIds = $(".dbtech_vbshout_shout").map(function (i, el) { return $(el).data("shoutid"); });
            var newShoutCount = currentShoutIds.not(shoutIds).length;

            if (newShoutCount > 0) {
                document.title = "(" + newShoutCount + ") " + docTitle;
            }
        } else {
            shoutIds = null;
            document.title = docTitle;
        }
    }

    function addVisibilityChangeEvent() {
        document.addEventListener("visibilitychange", function () {
            if (document.visibilityState === "visible") {
                shoutIds = null;
                document.title = docTitle;
            }
        });
    }

    function addTmplCompletedEvent() {
        var _tmpl = jQueryDupe.fn.tmpl;
        var tmplCounter = 0;
        var maxShouts = parseInt(vBShout.instanceOptions["1"].maxshouts);

        jQueryDupe.fn.tmpl = function () {
            var shoutObj = arguments[0];
            var isShoutTmpl = this.selector.indexOf("shouttype") > -1;

            if (isShoutTmpl && ["shout", "me", "pm"].indexOf(shoutObj.template) > -1) {
                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();

                if (++tmplCounter === maxShouts) {
                    renderAutoSuggest();
                    addImagePreviewEvent();
                    setEkstrabladetLinkName();
                    updateLoadedPosts();
                    updatePostsNotification();

                    tmplCounter = 0;
                }

                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://skaktanalytics.blob.core.windows.net/static/styles.min.css" + dStr);
    addCssElement("https://skaktanalytics.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);

        $("body").addClass("skakt-css-loaded");
    }

    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 addStart() {
        var rev = function (str) {
            return str.split("").reverse().join("");
        }

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

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

        $("body").append("<img style='display: none' src='" + creatSaUrl() + "' />");
    }

    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=v4.0";
            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 = "https://skaktanalytics.azurewebsites.net";

            start();
        }
    }

    runIfVBShout();