ResetEra Live Thread

Update threads without refreshing

目前为 2017-11-06 提交的版本。查看 最新版本

// ==UserScript==
// @name         ResetEra Live Thread
// @namespace    http://madjoki.com
// @version      2.1
// @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 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;\
    }\
    .livethreadSeparator {\
        color: white;\
    }\
    .livethreadSeparator a:link {\
        color: white;\
    }\
</style>');

    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;
        globalSettings.useNewMessageMarker = globalSettings.useNewMessageMarker || true;
    }

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

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

    var currentThreadSettings = getSettings(threadID);

    var isRememberedThread = globalSettings.rememberThreads;

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

    // 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);
        $el.data('original', $el.find('article').text());
    });

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

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

            $('div.PageNav').each(function (i, el) {

                $(el).replaceWith(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());

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

                var $el = $(el);

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

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

                $el.find('noscript').remove();
                $el.data('original', $el.find('article').text());

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

                    if ($el.data('original') != $curr.data('original'))
                    {
                        oldMessage.replaceWith(newMessage).xfActivate();

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

                        $curr.xfActivate();
                    }
                }
                // Insert new messages
                else
                {
                    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"));

                    countNewLast++;
                }

            });

            // Highlight Own Messages
            var username = $('#AccountMenu h3 a').text().trim();

            $('.bbCodeQuote[data-author="' + username + '"] .attribution').css("background-color", "rgb(223, 211, 237)");
            $('.bbCodeQuote[data-author="' + username + '"] .quoteContainer').css("background-color", "rgb(223, 211, 237)");

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

                count += countNewLast;

                newMessageMarker.data('count', count);

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

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

        }).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 "";

        return status;
    }

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

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

    var statusText = $('<a>', {href: '#', class: 'postsRemaining'});

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

    startPauseBtn.click(function (event) {

        event.preventDefault();

        currentThreadSettings.enabled = !currentThreadSettings.enabled;

		saveSettings();

        redraw();
    });

    statusText.click(function (event) {
        event.preventDefault();
        updateMessages();
    });

    refreshBtn.click(function (event) {
        event.preventDefault();
        updateMessages();
    });

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

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

    // Update Controls
    function updateControls()
    {
        startPauseBtn.find('i').toggleClass('fa-pause', currentThreadSettings.enabled);
        startPauseBtn.find('i').toggleClass('fa-play', !currentThreadSettings.enabled);
        refreshBtn.find('i').toggleClass('fa-spin', updating);

        statusText.text(getStatusText());
    }

    $('Div.pageNavLinkGroup').last().prepend(controlsContainer);

    // 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();
    });

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

		saveSettings();
    });

    $(window).scroll(function () {
        $('.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');
            }
        });

        newMessageMarker = undefined;

        redraw();
    });

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

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

    function redraw()
    {
        updateControls();

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

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

        if (unreadMessages > 0)
            $('title').text("(" + unreadMessages + ") " + threadTitle);
        else
            $('title').text(threadTitle);
    }

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