Combines the different type of anime releases on the seasonal page into one list. Each type can be individually toggled.
// ==UserScript==
// @name Combined seasonal list - MAL
// @namespace https://greasyfork.org/en/users/954974-crill0
// @version 0.3
// @description Combines the different type of anime releases on the seasonal page into one list. Each type can be individually toggled.
// @run-at document-end
// @author Crill0
// @match *://myanimelist.net/anime/season*
// @icon https://cdn.myanimelist.net/images/favicon.ico
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(async function() {
'use strict';
if (document.querySelector("div.navi-seasonal > div.horiznav_nav .navtab.horiznav_active")) return; // Schedule or Archive page is selected
const defaultSelected = await GM.getValue("seasonalSelectedTypes", ["TV (New)", "ONA", "OVA", "Movie"]); // Buttons selected on page load
const getCurrentSelected = () => [...document.querySelectorAll("ul.btn-seasonal li.btn-type.on")].map(b => b.innerText);
// replace buttons
document.querySelector("ul.btn-seasonal").innerHTML = [...document.querySelectorAll(".seasonal-anime-list > .anime-header")].reduce((hPrev, h) => hPrev + `<li class="btn-type ${defaultSelected.find(d => d === h.innerText) ? "on" : ""}" data-key="${h.innerText}">${h.innerText}</li>`, "");
document.querySelectorAll("ul.btn-seasonal li.btn-type").forEach(button => button.addEventListener("click", e => { // on click
if (button.classList.contains("on")) button.classList.remove("on");
else button.classList.add("on");
updateAnimeTypes();
GM.setValue("seasonalSelectedTypes", getCurrentSelected()); // store current selected
}))
GM_addStyle(".navi-seasonal .horiznav-nav-seasonal .btn-seasonal .btn-type:hover {background-color: #677493} .navi-seasonal .horiznav-nav-seasonal .btn-seasonal .btn-type.on:hover {background-color: #2c4fa4}");
const getSortFunction = () => {
const sortOrder = document.querySelector("span.btn-sort-order.selected").id;
switch(sortOrder) {
case 'members': {
const getMembers = (el) => {
const m = el.querySelector('.scormem-item.member').innerText.trim();
return m.endsWith('K') ? m.slice(0, -1) * 1000 : m.endsWith('M') ? m.slice(0, -1) * 1000000 : m;
}
return (elA, elB) => getMembers(elB) - getMembers(elA);
}
case 'score': {
const getScore = el => {
const score = el.querySelector('.scormem-item.score').innerText;
return score === "N/A" ? 0 : score;
}
return (elA, elB) => getScore(elB) - getScore(elA);
}
case 'title' : {
const getTitle = el => el.querySelector('.link-title').innerText;
return (elA, elB) => getTitle(elA).localeCompare(getTitle(elB));
}
case 'studio' : {
const getStudio = el => el.querySelectorAll('.property')[0].querySelector('.item').innerText;
return (elA, elB) => getStudio(elA).localeCompare(getStudio(elB));
}
case 'start_date': {
const getStartDate = el => Date.parse(el.querySelector('.info').firstChild.innerText);
return (elA, elB) => getStartDate(elA) - getStartDate(elB);
}
default:
return () => 0;
}
}
const sortFunction = getSortFunction();
const listsByType = [...document.querySelectorAll(".seasonal-anime-list")];
// add type tag to each title
listsByType.forEach(l => {
const type = l.querySelector(".anime-header").innerText;
l.querySelectorAll(".seasonal-anime").forEach(a => {
a.setAttribute("data-anime-type", type);
a.querySelector(".info").insertAdjacentHTML("beforeend", `<span class="item">${type}</span>`);
});
});
// generate combined list
const combinedList = document.createElement("div");
combinedList.className = "seasonal-anime-list js-seasonal-anime-list combined-list";
combinedList.innerHTML = `<div class="anime-header"></div>`;
listsByType.flatMap(l => [...l.querySelectorAll(".seasonal-anime")]).sort((elA, elB) => sortFunction(elA, elB)).forEach(a => combinedList.appendChild(a.cloneNode(true)));
document.querySelector(".js-categories-seasonal").insertAdjacentHTML("beforebegin", `<div class="js-categories-seasonal" style="padding-top: 0px;">${combinedList.outerHTML}</div>`);
// hides types that are not selected
const typeHideAnime = () => {
const selectedLists = getCurrentSelected();
document.querySelectorAll(".combined-list .seasonal-anime").forEach(a => {
if (!selectedLists.find(s => s === a.dataset.animeType)) a.setAttribute("data-anime-type-selected", false);
else a.setAttribute("data-anime-type-selected", true);
})
document.querySelector(".combined-list .anime-header").innerText = selectedLists.join(" - ");
}
GM_addStyle(`div[data-anime-type-selected="false"]{display: none !important;}`); // don't display unselected types
const updateAnimeCounter = () => {
const totalAnime = document.querySelectorAll(".seasonal-anime").length;
const shownAnime = totalAnime - document.querySelectorAll(`.seasonal-anime[style="display: none;"], .seasonal-anime[data-anime-type-selected="false"]`).length;
document.querySelector(".js-visible-anime-count").innerText = `${shownAnime}/${totalAnime}`;
}
const updateAnimeTypes = () => {
typeHideAnime();
updateAnimeCounter();
}
listsByType.forEach(l => l.remove()); // remove standard lists
updateAnimeTypes(); // hide types not selected
})();