ResetEra Live Thread

Update threads without refreshing

Version vom 09.05.2018. Aktuellste Version

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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

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

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

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

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

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.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         ResetEra Live Thread
// @namespace    http://madjoki.com
// @version      3.4
// @description  Update threads without refreshing
// @author       Madjoki
// @match        https://www.resetera.com/threads/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    var globalSettings = {
        rememberThreads: true,
        updateTime: 30,
        enabledByDefault: false,
        useNewMessageMarker: true,
    };

    var windowID = Math.floor(Math.random() * 100000);

    var timeoptions = [
        {
            name: "Fast (15s)",
            value: 15,
        },
        {
            name: "Normal (30s)",
            value: 30,
        },
        {
            name: "Slow (1m)",
            value: 60,
        },
        {
            name: "Very Slow (2m)",
            value: 120,
        },
    ];

    $('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 #AjaxProgress {\
        display: none !important;\
    }\
</style>');

    var highlightScript = $('script:contains("data-author")').first().text();

    function addoptions(el, values) {

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

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

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

    if (settingsJson !== null)
    {
        globalSettings = JSON.parse(settingsJson);

        // Update settings by defaults
        globalSettings.updateTime = globalSettings.updateTime || 30;
        globalSettings.enabledByDefault = globalSettings.enabledByDefault || false;

        if (globalSettings.useNewMessageMarker === undefined)
            globalSettings.useNewMessageMarker = true;
    }

    var defaultThreadSettings = {
        updateTime: globalSettings.updateTime,
        enabled: globalSettings.enabledByDefault,
    };

    var pageType = "";

    if (location.path == "/")
        return;

    pageType = "thread";

    var threadID = parseInt($('[name="type[post][thread_id]"]').val(), 10);

    var currentThreadSettings = getSettings(threadID);

    var isRememberedThread = globalSettings.rememberThreads;

    var pauseReason = null;

    var hasFocus = true;
    var newMessageMarkerLast;
    var newMessageMarker;

    if (currentThreadSettings === null)
    {
        isRememberedThread = globalSettings.rememberThreads && !hasSettings(threadID);
        currentThreadSettings = defaultThreadSettings;
    }
    else
    {
        isRememberedThread = true
    }

    function calculateNextUpdate()
    {
        var timer = currentThreadSettings.updateTime;

        // If we have more pages to load, use shorter timer
        if (currentPage < lastPage)
            timer = 5;

        timeToNextUpdate = timer;
    }

    function hasSettings(thread)
    {
        var key = "livethread_" + thread;

        return key in localStorage;
    }

    function getSettings(thread)
    {
        var stored = localStorage.getItem("livethread_" + thread);

        if (stored === null)
            return null;

        return JSON.parse(stored);
    }

    function setSettings(thread, settings)
    {
        localStorage.setItem("livethread_" + thread, JSON.stringify(settings));
    }

	function saveSettings()
	{
        if (isRememberedThread)
            setSettings(threadID, currentThreadSettings);
        else
            setSettings(threadID, null);

        localStorage.setItem("livethreadSettings", JSON.stringify(globalSettings));

        redraw();
	}

    var countNewLast = 0;
    var errors = 0;
    var updating = false;
    var lastUrl = window.location;
    var currentPage = $('div.PageNav').first().data('page') || 1;
    var lastLoadedPage = currentPage;
    var lastPage = $('div.PageNav').first().data('last') || 1;
    var threadTitle = $('title').text();
    var timeToNextUpdate = 60;
    calculateNextUpdate();

    if (currentPage != lastPage)
        currentThreadSettings.enabled = false;

    // Do not enable if no messages (ie. edit / reply page)
    if ($('#messageList > li').length === 0)
        return;

    $('#messageList > li').each(function (i, el) {
        var $el = $(el);

        $el.data('livethread-page', currentPage);
    });

	var timeout = setInterval(timerTick, 1000);

    function timerTick()
    {
		if (!currentThreadSettings.enabled)
			return;

        timeToNextUpdate--;

        if (timeToNextUpdate === 0)
        {
            updateMessages();
            timeToNextUpdate = currentThreadSettings.updateTime;
        }

        redraw();
    }

    // Inserts marker after last message
    function insertNotifi(text)
    {
        var $el = $('<div>', {'class': "newMessagesNotice livethreadSeparator"});

        $el.append($('<span>').text(text));

        $el.append($('<a href="#" class="pull-right"><i class="fa fa-close"></a>').click(function (event) {
            event.preventDefault();

            $el.prevAll('div.newMessagesNotice').remove();
            $el.prevAll('li.message').removeClass('livethread_unread').hide();

            $el.remove();
        }));

        $("#messageList").append($el);

        return $el;
    }

    // Get URL for current page
    function getCurrentURL()
    {
        var pageNav = $('div.PageNav[data-page]').first();

        currentPage = pageNav.data('page') || 1;
        lastPage = pageNav.data('last') || 1;

        if (pageNav.data('baseurl') === undefined)
            return window.location;

        if (lastPage > currentPage)
            currentPage++;

        return pageNav.data('baseurl').replace('{{sentinel}}', currentPage);
    }

    function updateMessages()
    {
        if (updating)
            return;

        updating = true;
        redraw();

        countNewLast = 0;

        var thisUrl = getCurrentURL();
        lastUrl = getCurrentURL();

        $('body').addClass('liveThread_loading');

        $.get(lastUrl, function (data) {

            // If new page insert marker and update history
            if (lastLoadedPage < currentPage)
            {
                window.history.pushState(null, null, thisUrl);
                lastUrl = thisUrl;

                insertNotifi("Page " + currentPage);

                lastLoadedPage = currentPage;
            }

            var node = $($.parseHTML(data, document, true));

            function decodeEmails()
            {
                if ($('[data-cfemail]').length > 0)
                {
                    var emailDecoder = node.find('script[src*="email-decode"]').attr('src');
                    $.getScript(emailDecoder);
                }
            }

            var topGroup = $('Div.pageNavLinkGroup').last();
            var botGroup = $('Div.pageNavLinkGroup').first();

            var newNav = node.find('div.PageNav').first();

            if (newNav.length)
            {
                var topNav = topGroup.find('div.PageNav');
                var botNav = botGroup.find('div.PageNav');

                if (topNav.length)
                    topNav.replaceWith(newNav.clone());
                else
                    topGroup.append(newNav.clone());

                if (botNav.length)
                    botNav.replaceWith(newNav.clone());
                else
                    botGroup.append(newNav.clone());
            }

            // To avoid reloading mesasges after posting
            $('input[name="last_date"]').val(node.find('input[name="last_date"]').val());
            $('input[name="last_known_date"]').val(node.find('input[name="last_known_date"]').val());

            var anyChanges = false;

            node.find('#messageList > li').each(function (i, el) {

                var $el = $(el);

                var id = $el.attr('id');

                var $curr = $('#' + id);

                var forchecks = $el.find('article').clone();

                forchecks.find('iframe').remove();
                forchecks.find('noscript').remove();

                // Sometimes bbcode has "X Said: <" and other times "X Said:<".
                // This makes them considered equal
                // Also IDs
                var newData = forchecks.html().replace(/\s+</g, "<").replace(/>\s+/g, ">").replace(/\W([a-f0-9]{4,})\W/g, "");

                // Update old message if changed
                if ($curr.length)
                {
                    var newMessage = $el.find('.messageInfo');
                    var oldMessage = $curr.find('.messageInfo');

                    var embeds = $curr.find('iframe');

                    var editTimeNew = $el.find('.editDate abbr').data('time');
                    var editTimeOld = $curr.find('.editDate abbr').data('time');

                    var hasDifferentContent = newData !== $curr.data('original') && $curr.data('original') !== undefined;
                    var hasEditTimeChanged = editTimeNew !== editTimeOld;

                    // Update if there's zero embeds
                    if (embeds.length == 0 && (hasDifferentContent || editTimeNew != editTimeOld))
                    {
                        // For debugging:
                        console.log("before");
                        console.log($curr.data('original'));
                        console.log("after");
                        console.log(newData);

                        anyChanges = true;
                        oldMessage.replaceWith(newMessage);
                        $curr.xfActivate();
                    }
                    else if ((hasDifferentContent) || (editTimeNew != editTimeOld))
                    {
                        // For debugging:
                        console.log("before");
                        console.log($curr.data('original'));
                        console.log("after");
                        console.log(newData);

                        anyChanges = true;
                        var edit = $('<a>', {'href': '#', 'class': 'refresh', 'text': 'Show Edited (Reset Embeds)'});

                        edit.click(function (ev) {
                            ev.preventDefault();
                            oldMessage.replaceWith(newMessage);
                            $curr.xfActivate();
                            decodeEmails();
                        });

                        $curr.find('.postDate .refresh').remove();

                        $curr.find('.postDate').append(edit);
                    }

                    $curr.data('original', newData);
                }
                // Insert new messages
                else
                {
                    anyChanges = true;

                    if (!hasFocus && globalSettings.useNewMessageMarker && newMessageMarker === undefined)
                    {
                        newMessageMarker = insertNotifi("");
                        newMessageMarker.data('count', 0);
                    }

                    $el.data('livethread-page', currentPage);
                    $el.addClass('livethread_unread');

                    $el.xfInsert('appendTo', $("#messageList"));

                    $el.data('original', newData);

                    countNewLast++;
                }

            });

            if (anyChanges)
            {
                try {
                    document.dispatchEvent(new CustomEvent("LiveThreadUpdate"));
                } catch (ignore) {}
            }

            decodeEmails();

            // Update message times
            XenForo._TimestampRefresh.refresh(null, true);

            // Highlight Own Messages
            jQuery.globalEval(highlightScript);

            var count = countNewLast;

            if (newMessageMarker !== undefined)
            {
                count = newMessageMarker.data('count') + countNewLast;

                newMessageMarker.data('count', count);

                newMessageMarker.find('span').text(count + " new messages");
            }

            
        }).always(function () {
            updating = false;
            calculateNextUpdate();
            redraw();

            $('body').removeClass('liveThread_loading');
        });
    }


    // Control Panel
    function updateForm()
    {
        addoptions($("#updateTime"), timeoptions);
        addoptions($("#updateTimeDefault"), timeoptions);

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

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

    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 getStatusText()
    {
        var status  = "";

        if (updating)
            status += "Updating";
        else if (currentThreadSettings.enabled)
        {
            status += "Next Update In " + timeToNextUpdate + " seconds";

            if (countNewLast > 0)
                status += " - " + countNewLast + " New Messages!";
        }
        else
            return pauseReason || "";

        return status;
    }

    // Build Controls
    $('a.postsRemaining').hide();

    var controlsContainer = $('<div>', {class: 'linkGroup'});

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

    $('Div.pageNavLinkGroup').last().prepend(controlsContainer);
    $('Div.pageNavLinkGroup').first().find('.linkGroup').after(controlsContainer.clone());

    $('.livethreadStartPause').click(function (event) {

        event.preventDefault();

        pauseReason = "";

        currentThreadSettings.enabled = !currentThreadSettings.enabled;

		saveSettings();

        redraw();
    });

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

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

    // Update Controls
    function updateControls()
    {
        $(".livethreadStartPause i").toggleClass('fa-pause', currentThreadSettings.enabled);
        $(".livethreadStartPause i").toggleClass('fa-play', !currentThreadSettings.enabled);
        $(".livethreadRefresh i").toggleClass('fa-spin', updating);

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

    // Build Settings
    $('Div.pageNavLinkGroup').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>\
       <li><label><input type="checkbox" id="liveThread_currentRemember" value="1"> Remember this thread</label></li>\
    </ul>\
    <h2 class="heading h1">Global Settings</h2>\
    <ul>\
			<li><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><input type="checkbox" id="liveThread_messageMarkers" value="1"> Insert Marker for New Messages</label></li>\
			<li><label>Default Update Speed: <select id="updateTimeDefault" class="textCtrl"></select></label></li>\
    </ul>\
</div>');

    $('#liveThread_currentRemember').change(function () {

        isRememberedThread = $('#liveThread_currentRemember').is(':checked');

        saveSettings();
    });

    $('#liveThread_enableByDefault').change(function () {

        globalSettings.enabledByDefault = $('#liveThread_enableByDefault').is(':checked');

        saveSettings();
    });

    $('#liveThread_messageMarkers').change(function () {

        globalSettings.useNewMessageMarker = $('#liveThread_messageMarkers').is(':checked');

        saveSettings();
    });

    $('#liveThread_remember').change(function () {

        globalSettings.rememberThreads = $('#liveThread_remember').is(':checked');

        saveSettings();
    });

    $('#updateTime').change(function () {
        currentThreadSettings.updateTime = parseInt($('#updateTime').val());

		saveSettings();

        if (currentThreadSettings.updateTime < timeToNextUpdate)
            calculateNextUpdate();

        redraw();
    });

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

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

    $('#updateTimeDefault').change(function () {
        globalSettings.updateTime = parseInt($('#updateTimeDefault').val());

		saveSettings();
    });

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

        newMessageMarker = undefined;

        redraw();
    });

    $(window).focus(function () {
        // Reset new messages on focus
        handleScroll();
        redraw();
        hasFocus = true;
        newMessageMarker = undefined;
    });

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

    function redraw()
    {
        updateControls();

        $('body').toggleClass('liveThread_enabled', currentThreadSettings.enabled);

        var unreadMessages = $('.livethread_unread').length;

        var newTitle = document.title;

        if (unreadMessages > 0)
            newTitle = "(" + unreadMessages + ") " + threadTitle;
        else
            newTitle = threadTitle;

        if (newTitle != document.title)
            document.title = newTitle;
    }

    redraw();
    updateForm();
})();