ResetEra Live Thread

Update threads without refreshing

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         ResetEra Live Thread
// @namespace    http://madjoki.com
// @version      4.0.15
// @description  Update threads without refreshing
// @author       Madjoki
// @match        https://metacouncil.com/threads/*
// @match        https://www.resetera.com/threads/*
// @match        https://bbs.io-tech.fi/threads/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/favico.js/0.3.10/favico.min.js
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    let favicon;

    // This is to disable scrolldown behaviour when XenForo insert "new messages" box.
    const original = XF.Message.insertMessages;

    XF.Message.insertMessages = function (dataHtml, $container, ascending, onInsert)
    {
        console.log(dataHtml, $container, ascending, onInsert);

        if (dataHtml.content.indexOf('js-newMessagesIndicator') > -1)
            return;

        original(dataHtml, $container, ascending, onInsert);
    }

    var favIconUpdate = function (count) {
        favicon.badge(count);
        favicon.badge(count);
    }

    if (window.location.host === 'www.resetera.com') {
        // Chrome Fix
        //let icon = $('link[rel*=icon]').first().clone();
        //$('link[rel*=icon]').remove();
        //$('head').append(icon);

        favicon = new Favico({
            animation: 'none',
            fontFamily: 'FontAwesome',
            fontStyle: 'normal'
        });

    }
    else {
        favicon = new Favico({
            animation: 'none',
            fontFamily: 'FontAwesome',
            fontStyle: 'normal'
        });
    }

    favIconUpdate();

    var timeoptions = [
        {
            name: "5s",
            value: 5,
        },
        {
            name: "10s",
            value: 10,
        },
        {
            name: "15s",
            value: 15,
        },
        {
            name: "30s",
            value: 30,
        },
        {
            name: "1m",
            value: 60,
        },
        {
            name: "2m",
            value: 120,
        },
    ];

    let defaults = {
        timer: 5,
        enabledByDefault: false,
    };

    let threadID = $('html').data('content-key');
    let userSettings = {}
    let threadSettings = {}
    let recentErrors = 0;
    let countNewMessages = 0;
    let updating = false;
    let enabled = false;
    let paused = false;
    let currentTimer = 120;
    let hasFocus = true;

    // Read Global Settings
    let settingsJson = localStorage.getItem("livethreadSettings");

    if (settingsJson !== null)
        userSettings = JSON.parse(settingsJson) || {};

    // Read Thread Settings
    let threadJson = localStorage.getItem("livethread_" + threadID);

    if (threadJson !== null)
        threadSettings = JSON.parse(threadJson) || {};

    let currentSettings = {}

    function updateSettings() {
        currentSettings = {
            ...defaults,
            ...userSettings,
            ...threadSettings
        }

        if (!("enabled" in currentSettings))
            currentSettings.enabled = currentSettings.enabledByDefault;

        if (!currentSettings.timer || currentSettings.timer < 0)
            currentSettings.enabled = false;

        enabled = currentSettings.enabled;
        paused = !enabled;
    }

    updateSettings();
    currentTimer = currentSettings.timer;

    function getPages(dom) {
        return {
            current: parseInt(dom.find('li.pageNav-page--current').first().text(), 10) || 0,
            next: parseInt(dom.find('.pageNav-page.pageNav-page--later').first().text()) || parseInt(dom.find('.pageNav-page').last().text()) || 0,
            last: parseInt(dom.find('.pageNav-page').last().text()) || 0
        };
    }

    function updateFavIcon() {
        if (countNewMessages > 0) {
            favIconUpdate(countNewMessages);
        }
        else if (currentSettings.enabled && !paused) {
            favIconUpdate('');
        }
        else {
            favIconUpdate(0);
        }
    }

    function addoptions(el, values) {

        $(el).find("option").remove();

        $(values).each(function (i, o) {
            $(el).append($("<option>", {text: o.name, value: o.value}));
        })
    }

    // CSS
    $('body').append(`<style>
    #livethreadPanel {
        display: none;
        text-align: center;
    }
    #livethreadPanel ul {
        display: inline-block;
        margin-bottom: 15px;
    }
    #livethreadPanel ul li {
        display: block;
        text-align: left;
    }
    #updateTime {
        margin-left: 5px;
        padding: 0px;
    }
    #updateTimeDefault {
        margin-left: 5px;
        padding: 0px;
    }
    body.darktheme #livethreadPanel ul {
       color: #8e50be;   /*dark theme only*/
    }
    .livethreadStatus {
        text-align: center;
    }
    .liveThread_enabled .globalAction {
        display: none !important;
    }
    .liveThreadControls a {
        padding: 5px;
    }
</style>`);

    function getTimeOfLastMessage()
    {
        return $('article.message time').last().data('time');
    }

    function getPageUrl(page)
    {
        $('meta[property="og:url"]').attr('content') + `page-${page}`;
    }

    // Get date of last message
    var $lastDate = $('input[name="last_date"]');
    var $container = $('.js-replyNewMessageContainer');
    var pages = getPages($('body'));
    var lastPageWithData = pages.current;

    // If zero messages, it's non thread page like reply page
    if ($('article.message').length === 0)
        return;

    // Pause if this isn't last page
    if (pages.current !== pages.last)
        paused = true;

    // Create Control Panel
    var controlsContainer = $('<div>', { class: 'block-outer-opposite liveThreadControls' });

    var statusText = $('<a>', { href: '#', class: 'livethreadStatus livethreadRefresh postsRemaining' });
    var startPauseBtn = $('<a>', { href: '#', class: 'livethreadStartPause' }).append($('<i>', { class: 'fa' }));
    var settingsBtn = $('<a>', { href: '#', class: 'livethreadSettings' }).append($('<i>', { class: 'fa fa-cog' }));
    var refreshBtn = $('<a>', { href: '#', class: 'livethreadRefresh' }).append($('<i>', { class: 'fa fa-refresh' }));

    controlsContainer.append(statusText);
    controlsContainer.append(startPauseBtn);
    controlsContainer.append(refreshBtn);
    controlsContainer.append(settingsBtn);

    $('.block-outer.block-outer--after').append(controlsContainer);

    // Build Settings
    $('.block-outer.block-outer--after').last().after('\
<div id="livethreadPanel" class="DiscussionListOptions secondaryContent">\
    <h2 class="heading h1">This Thread</h2>\
    <ul>\
       <li><label for="updateTime">Update Speed:</label> <select id="updateTime" class="textCtrl"></select></li>\
    </ul>\
    <h2 class="heading h1">Global Settings</h2>\
    <ul>\
			<li style="display: none"><label><input type="checkbox" id="liveThread_remember" value="1"> Remember New Threads by Default</label></li>\
			<li><label><input type="checkbox" id="liveThread_enableByDefault" value="1"> Enable By Default</label></li>\
			<li><label>Default Update Speed: <select id="updateTimeDefault" class="textCtrl"></select></label></li>\
			<li style="display: none"><label><input type="checkbox" id="liveThread_debug" value="1"> Log Debug Data to Console (only for testing)</label></li>\
    </ul>\
</div>');

    function saveSettings()
	{
        localStorage.setItem("livethread_" + threadID, JSON.stringify(threadSettings));
        localStorage.setItem("livethreadSettings", JSON.stringify(userSettings));

        updateForm();
	}

    $('#liveThread_enableByDefault').change(function () {
        userSettings.enabledByDefault = $('#liveThread_enableByDefault').is(':checked');
        saveSettings();
    });

    $('#liveThread_messageMarkers').change(function () {
        userSettings.useNewMessageMarker = $('#liveThread_messageMarkers').is(':checked');
        saveSettings();
    });

    $('#liveThread_remember').change(function () {
        userSettings.rememberThreads = $('#liveThread_remember').is(':checked');
        saveSettings();
    });

    $('#liveThread_debug').change(function () {
        userSettings.enableDebug = $('#liveThread_debug').is(':checked');
        saveSettings();
    });

    $('#updateTime').change(function () {
        const time = parseInt($('#updateTime').val());
        if (time)
            threadSettings.timer = parseInt(time);
        else
            delete threadSettings.timer;

		saveSettings();
    });

    $('#updateTimeDefault').change(function () {
        userSettings.timer = parseInt($('#updateTimeDefault').val());
		saveSettings();
    });

    // Control Panel
    function updateForm()
    {
        addoptions($("#updateTime"), [{
            name: "Default",
            value: 0,
        }, {
            name: "Disabled",
            value: -1,
        }, ...timeoptions]);
        addoptions($("#updateTimeDefault"), timeoptions);

        $("#updateTime option[value='" + threadSettings.timer + "']").attr("selected", true);
        $("#updateTimeDefault option[value='" + userSettings.timer + "']").attr("selected", true);

        $("#liveThread_remember").attr("checked", userSettings.rememberThreads);
        //$("#liveThread_messageMarkers").attr("checked", userSettings.useNewMessageMarker);
        $("#liveThread_enableByDefault").attr("checked", userSettings.enabledByDefault);
        //$('#liveThread_currentRemember').attr("checked", isRememberedThread);
        //$("#liveThread_debug").attr("checked", globalSettings.enableDebug);
    }

    updateForm();

    function insertMessagesAlternative(data) {
        console.log(data);

        var html = $.parseHTML(data.html.content);
        var $html = $(html);

        var pagesNew = getPages($html);

        if (pagesNew.current !== pages.current)
        {
            console.log("page changed", pagesNew, pages);

            pages = pagesNew;
            lastPageWithData = pages.current;

            history.pushState({}, "", `page-${pages.current}`);

            var $navNew = $html.find('.pageNavWrapper').first();
            $('.pageNavWrapper').html($navNew.html());
        }

        $html.find('article.message').each(function () {
            insertMessage($(this), $container, true);
        });

        updateFavIcon();

        updating = false;
    }

    function insertMessages(data) {

        if (data.message)
        {
            recentErrors++;
            return;
        }

        if (data.lastDate)
            $lastDate.val(data.lastDate);

        recentErrors = 0;

        if (data.html) {
            XF.setupHtmlInsert(data.html, function ($html, container, onComplete, onInsert) {
                // TODO: Check if DIV is there and load additional messages automatically
                var div = $html.children('div');

                if (div.length)
                {
                    console.log(div);
                }

                $html.each(function () {

                    if (!this.tagName) {
                        return;
                    }

                    if (this.tagName === 'DIV') {
                        console.log(this);

                        var $msg = $(this);

                        // TEMP
                        $container.append(this);
                    }

                    insertMessage($(this), $container, true);
                });

                if (onInsert) {
                    onInsert($html);
                }

                updateFavIcon();
                updating = false;
            });
        }
    }

    function insertMessage($message, $container, ascending) {
        if (!$message.data('author')) // Fix for empty messages
            return;

        // post-15795528
        var id = $message.attr('id');

        var $msg = $(`#${id}`);

        if ($msg.length)
        {
            console.log(`not inserting ${id}, already in page`);
            // TODO: update
            return;
        }

        countNewMessages++;

        var $firstChild = $container.children().first();

        //$message.hide();

        if ($firstChild.is('form') && !ascending) {
            $message.insertAfter($firstChild);
        }
        else if (!ascending) {
            $container.prepend($message);
        }
        else {
            $container.append($message);
        }

        //$message.xfFadeDown();
        $message.addClass('livethread_unread');

        XF.activate($message);
    }

    function loadMessages() {
        if (updating)
            return;

        if (lastPageWithData !== pages.last)
            loadMessagesAlternative();
        else
            loadMessagesFast();

        updating = true;
    }

    // Use api to get messages
    function loadMessagesFast() {
        XF.ajax('GET', 'new-posts', { after: getTimeOfLastMessage() }, insertMessages).always(function () { updating = false }, {useError: false});
        updateControls();
    }

    function loadMessagesAlternative() {
        const page = pages.next || pages.current;

        XF.ajax('GET', `page-${page}`, insertMessagesAlternative).always(function () { updating = false }, {useError: false});
    }

    function timer() {
        if (paused || !enabled)
            return;

        currentTimer--;

        // Delay if there's recent errors
        var errorDelay = 10000 * Math.min(5, recentErrors);

        if (currentTimer === 0) {
            loadMessages();
            currentTimer = currentSettings.timer + errorDelay;
        }

        updateControls();
    }

    function getStatusText() {
        var status = "";

        if (updating)
            status += "Updating";
        else if (currentTimer && !paused && enabled) {
            status += "Next Update In " + currentTimer + " seconds";

            //if (countNewLast > 0)
            //    status += " - " + countNewLast + " New Messages!";
        }
        else
            status = "Disabled";

        return status;
    }

    function updateControls() {
        $(".livethreadStartPause i").toggleClass('fa-pause', !paused);
        $(".livethreadStartPause i").toggleClass('fa-play', paused);
        $(".livethreadRefresh i").toggleClass('fa-spin', updating);

        $(".livethreadStatus").text(getStatusText());

        $('body').toggleClass('liveThread_enabled', !paused);
    }

    function isvisible($ele) {
        var lBound = $(window).scrollTop(),
            uBound = lBound + $(window).height(),
            top = $ele.offset().top,
            bottom = top + $ele.outerHeight(true);

        return (top > lBound && top < uBound) || (bottom > lBound && bottom < uBound) || (lBound >= top && lBound <= bottom) || (uBound >= top && uBound <= bottom);
    }

    function handleScroll() {
        $('.livethread_unread').each(function (i, el) {
            var $el = $(el);

            if (isvisible($el)) {
                $el.removeClass('livethread_unread');
                $el.prevAll('.livethread_unread').removeClass('livethread_unread');
            }
        });

        countNewMessages = $('.livethread_unread').length;
        updateFavIcon();
    }

    $(window).scroll(function () {
        handleScroll();
    });

    $(window).focus(function () {
        handleScroll();
        hasFocus = true;
    });

    $(window).focusout(function () {
        handleScroll();
        hasFocus = false;
    });

    updateControls();

    setInterval(timer, 1000);

    $('.livethreadRefresh').click(function (event) {
        event.preventDefault();
        loadMessages();
    });

     $('.livethreadStartPause').click(function (event) {
        event.preventDefault();
        enabled = true;
        paused = !paused;
        updateControls();
    });

    $('.livethreadSettings').click(function (event) {
        event.preventDefault();
        $('#livethreadPanel').toggle();
        //$('#livethreadPanel').scrollintoview();
    });

})();