// ==UserScript==
// @name [Retired] Backloggery - NP Extra Statuses
// @description Adds ability to filter "Now Playing" games under specific categories (on-hold, plan-to-play, etc.) & order them.
// @author Daku (admin@codeanimu.net)
// @namespace https://github.com/DakuTree/userscripts
// @homepage https://github.com/DakuTree/userscripts
// @homepageURL https://github.com/DakuTree/userscripts
// @supportURL https://github.com/DakuTree/userscripts/issues
// @include /^http[s]?:\/\/(?:www\.)?backloggery\.com\/(?:.(?!\.php))+$/
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
// @version 2.1.1
// @grant GM_addStyle
// ==/UserScript==
/*** OPTIONS ***/
const defaultCategories = {
"start" : {
//now-playing is used if none is specified.
"now-playing" : {
"description" : ""
}
},
"end" : {
"on-hold" : {
"title" : "On-Hold",
"description" : ""
},
"to-play" : {
"title" : "Plan to Play",
"description" : ""
}
}
};
$(function() {
GM_addStyle(`
.hd-desc {
display: block;
margin-bottom: 5px;
}
.us-game-scroll {
overflow-y: auto;
max-height: 285px;
overflow-x: hidden;
}
`);
//Case insensitive :contains || via: https://gist.github.com/jbcappell/2648373
$.extend($.expr[":"], {
"containsNC": function(elem, i, match, array) {
return (elem.textContent || elem.innerText || "").toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
}
});
//https://github.com/padolsey-archive/jquery.fn/blob/master/sortElements/jquery.sortElements.js
jQuery.fn.sortElements=(function(){var sort=[].sort;return function(comparator,getSortable){getSortable=getSortable||function(){return this;};var placements=this.map(function(){var sortElement=getSortable.call(this),parentNode=sortElement.parentNode,nextSibling=parentNode.insertBefore(document.createTextNode(''),sortElement.nextSibling);return function(){if(parentNode===this){throw new Error("You can't sort elements if any one is a descendant of another.");} parentNode.insertBefore(this,nextSibling);parentNode.removeChild(nextSibling);};});return sort.call(this,comparator).each(function(i){placements[i].call(getSortable.call(this));});};})();
/**** SCRIPT ****/
let userCategories = getCategories();
$.each($.extend(defaultCategories.start, userCategories, defaultCategories.end), function(tag, data) {
let cateTitle = data.title,
cateDescription = data.description,
cateScroll = data.scroll,
cateMaxHeight = data.maxHeight;
//Validate data.
if(!tag.match(/^[a-zA-Z0-9-_\(\)\.\?\#\~\:\{\}\"\'\, ]+$/)) {
console.warn(`User category tag contains invalid characters: ${tag}`);
return true;
} else if(!cateTitle.match(/^[a-zA-Z0-9-_\(\)\.\?\#\~\:\{\}\"\'\, ]+$/)) {
console.warn(`User title contains invalid characters: ${cateTitle}`);
return true;
} else if(!cateDescription.match(/^[a-zA-Z0-9-_\(\)\.\?\#\~\:\{\}\"\'\, ]*$/)) {
console.warn(`User description contains invalid characters: ${cateDescription}`);
return true;
}
//The now-playing already exists, however the user can add a description which we need to add to account for.
if(tag === 'now-playing' && cateDescription) {
let header = $('#intro h1:containsNC("Now Playing")');
header.after(
$('<span/>', {class: 'hd-desc', text: cateDescription})
);
header.attr('style', 'margin-bottom: 3px');
return true;
}
//Check game list for series containing category.
let games = $(`.npgame:containsNC([${tag}])`);
if(games.length > 0){
let intro = $('#intro');
intro.append(
$('<h1/>', {text: cateTitle, style: 'margin-top: 12px;'})
);
if(cateDescription.length > 0) {
intro.append(
$('<span/>', {class: 'hd-desc', text: cateDescription, style: 'margin-top: 3px;'})
);
}
let games_container = $('<div/>', {class: 'es-games'});
if(cateScroll) {
games_container.addClass('us-game-scroll');
}
if(cateMaxHeight) {
games_container.css('max-height', parseInt(cateMaxHeight));
}
$(games).each(function(){
let game = $(this),
gameDescription = game.children(':eq(4)').text().trim(),
priority = -1;
game.parent().attr('data-text', gameDescription); //Keep the original description for editting purposes.
//Check if game also has priority
let pArr = gameDescription.match(/\[p\:([0-9]+)]/);
if(pArr) {
priority = parseInt(pArr[1]);
}
game.attr('data-priority', priority);
//TODO: We should also add some form of icon.
//Remove tags from visible description.
$(game).children(':eq(4)').find('span').text(function() {
//Check: Is there a better way to do this regex? Lots of duplication here..
let re = new RegExp('(?:^[\\s]*\\['+tag+'\\][\\s]*|[\\s]*\\['+tag+'\\][\\s]*$)','i');
//TODO: Remove priority after we add prio icon.
// var re = new RegExp('(?:^[\\s]*\\[p\\:[0-9]+\\][\\s]*|[\\s]*\\[p\\:[0-9]+\\][\\s]*$)','i');
return $(this).text().replace(re, '');
});
let game_info = $(game).next();
let game_container = $('<div/>', {class: 'es-game'})
.append(game)
.append(game_info); //Backloggery stores the "more" data in the next div, which lacks proper IDs.
$(game_container).appendTo(games_container);
});
$(games_container).appendTo('#intro');
}
});
//Stick non-tagged games in divs.
let games_container = $('<div/>', {class: 'es-games'});
$('#intro > .npgame').each(function(){
let game = $(this);
let game_info = $(game).next();
let game_container = $('<div/>', {class: 'es-game'})
.append(game)
.append(game_info); //Backloggery stores the "more" data in the next div, which lacks proper IDs.
$(game_container).appendTo(games_container);
});
$(games_container).insertAfter($('#intro > h1:first-of-type + .hd-desc') || $('#intro > h1:first-of-type'));
sortGames();
/*****************************************/
function getCategories() {
let categoryMatch = $('#note').text().match(/==NPES-START==([\s\S]+)==NPES-END==/),
categories = {};
if(categoryMatch) {
try {
categories = JSON.parse(categoryMatch[1]);
} catch (e) {
console.error(e);
}
}
return categories;
}
function sortGames() {
$('.es-games').each(function() {
$(this).find('.es-game').sortElements(function(a, b) {
let a_game = $(a).find('> .npgame'),
b_game = $(b).find('> .npgame');
let a_platform = a_game.find('> div:eq(2)').text().trim(),
b_platform = b_game.find('> div:eq(2)').text().trim(),
a_priority = a_game.attr('data-priority'),
b_priority = b_game.attr('data-priority');
//Sort by priority first
if(a_priority > b_priority) {
return -1;
} else if(a_priority < b_priority) {
return 1;
} else {
//Then sort by platform.
if(a_platform > a_platform) {
return 1;
} else if(a_platform < b_platform) {
return -1;
} else {
//Then sort by title.
let a_title = $(a_game).find('> div:eq(3)').text().trim(),
b_title = $(b_game).find('> div:eq(3)').text().trim();
return (a_title > b_title) ? 1 : (a_title < b_title) ? -1 : 0;
}
}
});
});
}
});