Skip to content

Instantly share code, notes, and snippets.

@ChiriVulpes
Last active August 12, 2022 07:53
Show Gist options
  • Save ChiriVulpes/07d62e7ed53ff97e450b3d9f5a1e25a6 to your computer and use it in GitHub Desktop.
Save ChiriVulpes/07d62e7ed53ff97e450b3d9f5a1e25a6 to your computer and use it in GitHub Desktop.

Install

  1. Install extensions that can add custom JavaScript & CSS to pages.
    • On Chrome, I use User JavaScript and CSS.
    • On Firefox, my girlfriend and others have had success with Tampermonkey and Stylish.
  2. In your extension for JavaScript, paste the contents of Scribble Garbage Vaporiser.js
  3. In your extension for CSS, paste the contents of Scribble Garbage Vaporiser.css
  4. Go to Scribble Hub. Should have worked. 😁

Use

  • Right click on a series link: Hide series.
  • Right click on an author link: Hide all series by that author. (The way it does this requires seeing series links by their author links, so it isn't actually capable of hiding series in "Trending" or "Latest Series" automatically — it requires having seen them in "Latest Updates" or elsewhere.)
  • Right click on a tag link: Hide all stories with a specific tag. (This works similarly to author hiding, but requires visiting the main series page for the story. Information about the series is blurred so you don't have to see it.)
  • Right click on the Scribble Hub logo: Toggle whether series are completely removed from the page or simply transparent/blurred.
/* We replace series titles with links which don't display as block by default */
a.fic_title {
display: block;
}
.hidden-series:not(body) {
opacity: 0.6;
}
.hidden-series .novel_carousel_img .hidden-textlink img {
filter: blur(15px);
}
.hidden-series .m_img_fic img {
filter: blur(10px);
}
.m_img_fic {
overflow: hidden;
height: 71px;
width: 50px;
}
.hidden-series .novel_carousel_img .hidden-textlink:first-of-type {
overflow: hidden;
height: 142px;
width: 100px;
border-radius: 5px;
display: inline-block;
}
html body .hidden-textlink, html body .hidden-textlink *,
.showing-hidden .header-title-main {
color: #9060ff !important;
}
.hidden-textlink:hover,
.showing-hidden .header-title-main:hover {
opacity: 0.5;
}
:root:not(.showing-hidden) .hidden-series:not(body) {
display: none !important;
}
/* Hidden series count on main page */
:root:not(.showing-hidden) .hidden-series + tr > td {
--height: 40px;
--margin: -5px;
padding-top: calc(var(--height) - var(--margin)) !important;
position: relative;
}
:root:not(.showing-hidden) .hidden-series + tr > td::before {
content: var(--hidden-count-text);
display: block;
position: absolute;
top: var(--margin);
padding: 9px 5px;
left: 0;
width: 100%;
height: var(--height);
border-bottom: 1px solid #32393f;
font-style: italic;
color: #656e76;
background-color: #191d20;
}
.hidden-series + tr { --hidden-count-text: "(1 hidden series)"; }
.hidden-series + .hidden-series + tr { --hidden-count-text: "(2 hidden series)"; }
.hidden-series + .hidden-series + .hidden-series + tr { --hidden-count-text: "(3 hidden series)"; }
.hidden-series + .hidden-series + .hidden-series + .hidden-series + tr { --hidden-count-text: "(4 hidden series)"; }
.hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + tr { --hidden-count-text: "(5 hidden series)"; }
.hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + tr { --hidden-count-text: "(6 hidden series)"; }
.hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + tr { --hidden-count-text: "(7 hidden series)"; }
.hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + tr { --hidden-count-text: "(8 hidden series)"; }
.hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + tr { --hidden-count-text: "(9 hidden series)"; }
.hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + .hidden-series + tr { --hidden-count-text: "(10 hidden series)"; }
/* Undo prompt */
.scribble-garbage-vaporiser-undo {
position: fixed;
inset: auto auto 20px 20px;
width: fit-content;
height: fit-content;
z-index: 9999999999;
animation: scribble-garbage-vaporiser-undo-in .3s ease-out forwards;
}
.scribble-garbage-vaporiser-undo-hidden {
animation: scribble-garbage-vaporiser-undo-out .3s ease-in forwards;
}
@keyframes scribble-garbage-vaporiser-undo-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scribble-garbage-vaporiser-undo-out {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(10px);
}
}
/* Blur potentially-problematic elements on page for hidden series */
:root:not(.showing-hidden) body.hidden-series .fic_image:not(:hover) img {
filter: blur(20px);
box-shadow: none;
}
:root:not(.showing-hidden) body.hidden-series .fic_image {
overflow: hidden;
background: black;
position: relative;
width: 180px;
height: 259px;
box-shadow: 0 0 16px 2px rgb(0 0 0 / 24%);
}
:root:not(.showing-hidden) body.hidden-series .fic_image::after {
content: "";
display: block;
position: absolute;
inset: 0;
background: #fff1;
}
:root:not(.showing-hidden) .wi_fic_showtags {
display: block;
}
:root:not(.showing-hidden) body.hidden-series :is(.wi_fic_desc, .wi_fic_showtags):not(:hover),
:root:not(.showing-hidden) body.hidden-series .toc_ol:not(:hover) .toc_a {
filter: blur(10px);
}
/* SAVE CONVERT */
for (let key of Object.keys(localStorage)) {
const scribbleUrlRegex = /https:\/\/www\.scribblehub\.com\/(series|profile)\/\d+\/,/;
if (scribbleUrlRegex.test(key)) {
localStorage.setItem(key.replace(scribbleUrlRegex, ""), localStorage.getItem(key))
localStorage.removeItem(key);
}
}
//////////////////////////////////
// Produce hideable IDs from URLs
let seriesIdRegex = /https:\/\/www.scribblehub.com\/series\/(\d+)\//;
function getSeriesId (seriesLink) {
const seriesId = seriesLink.match(seriesIdRegex) || [];
if (!seriesId)
throw new Error(`Failed to get series ID, href: ${seriesLink}`);
return seriesId[1];
}
let authorIdRegex = /https:\/\/www.scribblehub.com\/profile\/(\d+)\//;
function getAuthorId (authorLink) {
const authorId = authorLink.match(authorIdRegex) || [];
if (!authorId)
throw new Error(`Failed to get author ID, href: ${authorLink}`);
return authorId[1];
}
let tagIdRegex = /https:\/\/www.scribblehub.com\/tag\/([^\/]+)\//;
function getTagId (tagLink) {
const tagId = tagLink.match(tagIdRegex) || [];
if (!tagId)
throw new Error(`Failed to get tag ID, href: ${tagLink}`);
return tagId[1];
}
// replace series titles with clickable links
var seriesSeriesName = document.querySelector(".novel-container .fic_title");
if (seriesSeriesName) {
const link = document.createElement("a");
link.classList.add("fic_title");
link.href = location.pathname;
link.textContent = seriesSeriesName.textContent;
seriesSeriesName.replaceWith(link);
}
// replace author names with clickable links
var profileAuthorName = document.querySelector(".profile .auth_name_fic");
if (profileAuthorName) {
const link = document.createElement("a");
link.classList.add("auth_name_fic");
link.href = location.pathname;
link.textContent = profileAuthorName.textContent;
profileAuthorName.replaceWith(link);
}
//////////////////////////////////////
// Toggling whether things are hidden
let lastPrompt;
async function hidePrompt(hidingPrompt = lastPrompt) {
if (!hidingPrompt || hidingPrompt.classList.contains("scribble-garbage-vaporiser-undo-hidden"))
return; // already hiding
hidingPrompt.classList.add("scribble-garbage-vaporiser-undo-hidden");
await sleep(1000);
hidingPrompt.remove();
if (lastPrompt === hidingPrompt)
lastPrompt = undefined;
}
async function toggleHidden(id, name, displayUndo = true) {
const isHidden = !localStorage.getItem(id);
if (isHidden)
localStorage.setItem(id, true);
else
localStorage.removeItem(id);
updateHidden();
// early exit if no display undo
if (!displayUndo)
return;
hidePrompt();
const undoPrompt = document.createElement("button");
undoPrompt.classList.add("scribble-garbage-vaporiser-undo");
undoPrompt.innerHTML = `Undo ${isHidden ? "hiding" : "showing"} <i>${name}</i>`;
undoPrompt.addEventListener("click", () => {
if (undoPrompt.classList.contains("scribble-garbage-vaporiser-undo-hidden"))
return; // can't click, hidden
toggleHidden(id, name, false);
hidePrompt(undoPrompt);
});
document.body.appendChild(undoPrompt);
lastPrompt = undoPrompt;
await sleep(5000);
hidePrompt(undoPrompt);
}
const seriesSelectors = [
".slick-slide", // carousel stories
"#main_releases > tbody > tr", // latest updates
".search_main_box", // tag page and probably searches page too?
".ficsimilar_body/parent", // similar series
".fic_title/body", // series page
];
/**
* Get the wrapper element for a series by its link, usually to hide it
*/
function getSeriesDisplay (seriesLink) {
for (let seriesSelector of seriesSelectors) {
const instructions = [];
let index;
while ((index = seriesSelector.lastIndexOf("/")) !== -1) {
instructions.unshift(seriesSelector.slice(index + 1));
seriesSelector = seriesSelector.slice(0, index);
}
let element = seriesLink.closest(seriesSelector);
if (element) {
for (const instruction of instructions) {
if (instruction === "parent")
element = element?.parentElement;
if (instruction === "body")
element = document.body;
}
}
if (element)
return element;
}
}
////////////////////////////////////////////////////
// Functions for updating whether things are hidden
function isHiddenSeries(seriesId) {
return localStorage.getItem(`hiddenSeries.${seriesId}`);
}
function isHiddenAuthor(authorId) {
return localStorage.getItem(`hiddenAuthor.${authorId}`);
}
function isHiddenTag(tagId) {
return localStorage.getItem(`hiddenTag.${tagId}`);
}
function queryAllSeriesLinks (container = document) {
return container.querySelectorAll("a[href^='https://www.scribblehub.com/series/'], a[href^='/series/']");
}
function queryAllAuthorLinks (container = document) {
return container.querySelectorAll("a[href^='https://www.scribblehub.com/profile/'], a[href^='/profile/']");
}
function queryAllTagLinks (container = document) {
return container.querySelectorAll("a[href^='https://www.scribblehub.com/tag/'], a[href^='/tag/']");
}
function updateHidden () {
updateHiddenSeries();
updateHiddenAuthors();
updateHiddenTags();
// hidden authors/tags can hide series, so we check series again
updateHiddenSeries();
}
function updateHiddenSeries() {
const seriesWrapperElements = Array.from(queryAllSeriesLinks())
.map(seriesLink => [seriesLink, getSeriesDisplay(seriesLink)]);
for (const [seriesLink, seriesWrapper] of seriesWrapperElements) {
const hiddenSeries = isHiddenSeries(getSeriesId(seriesLink.href));
seriesWrapper?.classList.toggle("hidden-series", hiddenSeries);
seriesLink?.classList.toggle("hidden-textlink", hiddenSeries);
}
}
function updateHiddenAuthors() {
const authorElements = Array.from(queryAllAuthorLinks())
.map(authorLink => [authorLink, getSeriesDisplay(authorLink)]);
for (const [authorLink, seriesWrapper] of authorElements) {
const hiddenAuthor = isHiddenAuthor(getAuthorId(authorLink.href));
authorLink?.classList.toggle("hidden-textlink", hiddenAuthor);
if (hiddenAuthor && seriesWrapper && !seriesWrapper.classList.contains("hidden-series")) {
// hidden author with a series that hasn't been hidden yet
for (const seriesLink of queryAllSeriesLinks(seriesWrapper)) {
const seriesId = `hiddenSeries.${getSeriesId(seriesLink.href)}`;
localStorage.setItem(seriesId, true);
seriesWrapper.classList.toggle("hidden-series", true);
break;
}
}
}
}
function updateHiddenTags() {
for (const tagLink of queryAllTagLinks()) {
const hiddenTag = isHiddenTag(getTagId(tagLink.href));
tagLink?.classList.toggle("hidden-textlink", hiddenTag);
if (hiddenTag) {
// hidden tag on a series that hasn't been hidden yet
const seriesId = `hiddenSeries.${getSeriesId(location.href)}`;
localStorage.setItem(seriesId, true);
document.body.classList.toggle("hidden-series", true);
}
}
}
const STORAGE_KEY_SHOWING_HIDDEN = "showing-hidden";
function updateShowingHidden(shown = localStorage.getItem(STORAGE_KEY_SHOWING_HIDDEN)) {
document.documentElement.classList.toggle("showing-hidden", shown);
}
// support for hidden series on profile (which can be toggled hidden/not)
const profileSeriesWrapper = document.querySelector(".p_load_series");
if (profileSeriesWrapper)
new MutationObserver(updateHidden)
.observe(profileSeriesWrapper, { childList: true });
/**
* Main event handler — everything is by contextmenu interactions
*/
window.addEventListener("contextmenu", event => {
if (location.pathname === "/reading-list/")
return;
if (event.target.closest(".header-title-main")) {
// toggle whether hidden stuff is displayed (with special css of course)
const isShown = !localStorage.getItem(STORAGE_KEY_SHOWING_HIDDEN);
if (isShown)
localStorage.setItem(STORAGE_KEY_SHOWING_HIDDEN, true);
else
localStorage.removeItem(STORAGE_KEY_SHOWING_HIDDEN);
updateShowingHidden(isShown);
event.preventDefault();
return;
}
// toggle things marked as garbage
const seriesLink = event.target.closest("a[href^='https://www.scribblehub.com/series/'], a[href^='/series/']");
if (seriesLink) {
toggleHidden(`hiddenSeries.${getSeriesId(seriesLink.href)}`, seriesLink?.closest(".novel_carousel_img")?.textContent ?? seriesLink.textContent);
event.preventDefault();
return;
}
const authorLink = event.target.closest("a[href^='https://www.scribblehub.com/profile/'], a[href^='/profile/']");
if (authorLink) {
toggleHidden(`hiddenAuthor.${getAuthorId(authorLink.href)}`, authorLink.textContent);
event.preventDefault();
return;
}
const tagLink = event.target.closest("a[href^='https://www.scribblehub.com/tag/'], a[href^='/tag/']");
if (tagLink) {
toggleHidden(`hiddenTag.${getTagId(tagLink.href)}`, tagLink.textContent);
event.preventDefault();
return;
}
});
updateHidden();
updateShowingHidden();
/////////////
// Utilities
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment