ResetEra Live Thread

Update threads without refreshing

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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

})();