Greasy Fork is available in English.
Show all titles from Continue Watching
// ==UserScript==
// @name CR - Continue Watching ALL
// @namespace https://greasyfork.org/users/1060113
// @version 1.01
// @description Show all titles from Continue Watching
// @author Hato4PL
// @match *://www.crunchyroll.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=crunchyroll.com
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
let isDimmed = 'dropdown-text-option--is-dimmed--t7bFy';
let isSomeHidden = false;
//Show All Titles from Continue Watching
const observer = new MutationObserver((mutationList, observer) => {
for (const mutation of mutationList) {
if (mutation.type === "childList" && mutation.addedNodes.length) {
//Show all available entries and hide hidden episodes/series
if ( (mutation.target.matches("[class*='container-']") && mutation.addedNodes[0].matches(".erc-history-collection")) || (mutation.target.matches("[class*='app-layout__content-']") && mutation.addedNodes[0].querySelector(".erc-history-collection") && !mutation.addedNodes[0].querySelector(".erc-history-collection [class*='playable-card-placeholder-']")) ) {
showAll();
if (isSomeHidden) addShowHiddenButton();
}
//Add options to dropdown menu
if ( (mutation.target.matches("[class*='playable-card-more-options-']")) || (mutation.target.matches("body") && mutation.addedNodes[0].matches("[class*='dropdown-content-']")) ) {
let isMobile = (mutation.target.matches("[class*='playable-card-more-options-']")) ? false : true;
let accountID = getAccountID();
let hiddenEpisodes = JSON.parse(localStorage.getItem("hiddenEpisodes."+accountID));
let hiddenSeries = JSON.parse(localStorage.getItem("hiddenSeries."+accountID));
let node = mutation.addedNodes[0];
let div = (!isMobile) ? node.querySelector("[class*='dropdown-content__scrollable-']") : node.querySelector("[class*='dropdown-content__children-']");
let parent = (!isMobile) ? node.closest('.collection-item'): document.querySelector("[aria-controls='"+node.id+"']").closest('.collection-item');
if (!isMobile) parent.style.zIndex = '2'; //fix overlapping
let series = getSeriesID(parent);
let episode = getEpisodeID(parent);
let divHE = div.cloneNode(true);
updateIsDimmed(div);
if (div.querySelector("[class*='dropdown-text-option--is-disabled-']")) {
let p = divHE.querySelector("[class*='dropdown-text-option-']");
p.classList.forEach(c => {
if (c.includes('dropdown-text-option--is-disabled-')) p.classList.remove(c);
});
p.classList.add(isDimmed);
p.tabIndex = 0;
p.removeAttribute('aria-disabled');
p.removeAttribute('style');
}
let isHiddenE = (hiddenEpisodes && hiddenEpisodes.some((x) => x == episode));
divHE.querySelector('p').innerText = (isHiddenE) ? 'Unhide this Episode' : 'Hide this Episode';
let divHS = divHE.cloneNode(true);
let isHiddenS = (hiddenSeries && hiddenSeries.some((x) => x == series));
divHS.querySelector('p').innerText = (isHiddenS) ? 'Unhide this Series' : 'Hide this Series';
div.parentNode.insertBefore(divHE, div.nextSibling);
div.parentNode.insertBefore(divHS, divHE.nextSibling);
divHE.addEventListener("click", (e) => {
node.previousSibling.click();
let hiddenEpisodes = JSON.parse(localStorage.getItem("hiddenEpisodes."+accountID));
if (isHiddenE) {
//Delete episode from hidden list
hiddenEpisodes.splice(hiddenEpisodes.indexOf(episode), 1);
(hiddenEpisodes.length) ? localStorage.setItem("hiddenEpisodes."+accountID, JSON.stringify(hiddenEpisodes)) : localStorage.removeItem("hiddenEpisodes."+accountID);
if (!isHiddenS) deleteHiddenMark(parent);
} else {
//Add episode to hidden list
hiddenEpisodes = (hiddenEpisodes) ? hiddenEpisodes : [];
hiddenEpisodes.push(episode);
localStorage.setItem("hiddenEpisodes."+accountID, JSON.stringify(hiddenEpisodes));
switch (getHiddenButtonStatus()) {
case 'hide-hidden':
setTimeout(() => { parent.style.display = 'none'; }, 0);
break;
case 'show-hidden':
addHiddenMark(parent);
break;
}
addShowHiddenButton();
}
});
divHS.addEventListener("click", (e) => {
node.previousSibling.click();
let hiddenSeries = JSON.parse(localStorage.getItem("hiddenSeries."+accountID));
if (isHiddenS) {
//Delete series from hidden list
hiddenSeries.splice(hiddenSeries.indexOf(series), 1);
(hiddenSeries.length) ? localStorage.setItem("hiddenSeries."+accountID, JSON.stringify(hiddenSeries)) : localStorage.removeItem("hiddenSeries."+accountID);
if (!isHiddenE) deleteHiddenMark(parent);
} else {
//Add series to hidden list
hiddenSeries = (hiddenSeries) ? hiddenSeries : [];
hiddenSeries.push(series);
localStorage.setItem("hiddenSeries."+accountID, JSON.stringify(hiddenSeries));
switch (getHiddenButtonStatus()) {
case 'hide-hidden':
setTimeout(() => { parent.style.display = 'none'; }, 0);
break;
case 'show-hidden':
addHiddenMark(parent);
break;
}
addShowHiddenButton();
}
});
}
}
//fix overlapping
if (mutation.type === "childList" && mutation.removedNodes.length && mutation.target.matches("[class*='playable-card-more-options-']")) {
mutation.target.closest('.collection-item').style.zIndex = '';
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
//
function updateIsDimmed(div) {
if (div.querySelector("[class*='dropdown-text-option--is-dimmed-']")) {
div.querySelector("[class*='dropdown-text-option--is-dimmed-']").classList.forEach(c => {
if (c.includes('dropdown-text-option--is-dimmed-')) isDimmed = c;
});
}
}
//Add show hidden button
function addShowHiddenButton() {
let button = document.querySelector('.erc-history-collection').previousSibling.querySelector("[class*='feed-header__view-link-']");
if (button.getAttribute('data-t') == 'view-history-btn') {
let buttonC = button.cloneNode(true);
buttonC.removeAttribute('href');
buttonC.setAttribute('data-t', 'hide-hidden');
buttonC.querySelector("[class*='feed-header__view-link-text--']").innerText = 'Show Hidden';
let path = buttonC.querySelector("path");
path.setAttribute('d', 'M5 5 20 5l0 15-15 0zM7 18l11 0 0-11-11 0z');
let pathC = path.cloneNode(true);
pathC.setAttribute('d', 'M8.5 11 9.7 10.4 11.5 14 15.08 8.99 16.5 10 11.5 17Z');
pathC.style.display = 'none';
path.parentNode.insertBefore(pathC, path.nextSibling);
button.parentNode.insertBefore(buttonC, button);
buttonC.addEventListener("click", (e) => {
e.preventDefault();
buttonC.blur();
switch (buttonC.getAttribute('data-t')) {
case 'hide-hidden':
buttonC.setAttribute('data-t', 'show-hidden');
pathC.style.display = '';
showAll(false);
break;
case 'show-hidden':
buttonC.setAttribute('data-t', 'hide-hidden');
pathC.style.display = 'none';
showAll();
break;
}
});
}
}
//Get hidden button current status
function getHiddenButtonStatus() {
let button = document.querySelector('.erc-history-collection').previousSibling.querySelector("[class*='feed-header__view-link-']");
return button.getAttribute('data-t');
}
//Show all titles from Continue Watching and hide hidden episodes/series
function showAll(hideHidden = true) {
let accountID = getAccountID();
let hiddenEpisodes = JSON.parse(localStorage.getItem("hiddenEpisodes."+accountID));
let hiddenSeries = JSON.parse(localStorage.getItem("hiddenSeries."+accountID));
document.querySelectorAll('.erc-history-collection .collection-item').forEach(div => {
div.style.display = 'block';
let series = getSeriesID(div);
let episode = getEpisodeID(div);
if ((hiddenEpisodes && hiddenEpisodes.some((x) => x == episode)) || (hiddenSeries && hiddenSeries.some((x) => x == series))) {
if (hideHidden) {
div.style.display = 'none';
} else {
addHiddenMark(div);
}
isSomeHidden = true;
}
});
}
//Add hidden mark from thumbnail
function addHiddenMark(node) {
if (!node.querySelector("[class*='playable-thumbnail__duration-'][data-t='hidden']")) {
node.querySelectorAll("[class*='playable-thumbnail__duration-']").forEach(div => {
let divC = div.cloneNode(true);
divC.innerText = 'Hidden';
divC.style.left = '0.25rem';
divC.style.top = '0.25rem';
divC.style.height = 'fit-content';
divC.setAttribute('data-t', 'hidden');
div.parentNode.insertBefore(divC, div.nextSibling);
});
}
}
//Remove hidden mark from thumbnail
function deleteHiddenMark(node) {
node.querySelectorAll("[class*='playable-thumbnail__duration-'][data-t='hidden']").forEach(div => {
div.remove();
});
}
//Get current profile ID
function getAccountID() {
let a = document.querySelector("[class*='user-menu-account-item-']");
return (a) ? a.href.split('/').slice(-1).pop() : JSON.parse(localStorage.getItem("ajs_user_traits")).profileId;
}
//Get series ID
function getSeriesID(node) {
let a = node.querySelector("[class*='playable-card-hover__secondary-title-link-']");
if (!a) a = node.querySelector("[class*='playable-card__show-link-']"); //mobile version
return a.href.split('/').slice(-2,-1).pop();
}
//Get episode ID
function getEpisodeID(node) {
return node.querySelector("[class*='playable-card__title-link-']").href.split('/').slice(-2,-1).pop();
}
})();