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