// ==UserScript== // @name MLPFicReviews Extras // @namespace http://ponepaste.org/5555 // @version 0.2 // @description Adds a few handy functions to mlpficreviews.org.uk // @author Fillyanon // @match http://www.mlpficreviews.org.uk/* // @grant none // ==/UserScript== const style = ` body { background-image: url("https://u.smutty.horse/mdqwfehyeeo.png"); background-attachment: fixed; background-repeat: no-repeat; background-size: 100%; } .page { width: 1100px; background: #eef2ff; padding: 0; } h1 { text-align: center; font-size: 2.5rem; padding: 1rem; background: hsl(60, 60%, 90%); } h1::before { content: url("https://static.fimfiction.net/images/logo-2x.png"); display: block; } a { text-decoration: none; } h2 { margin-top: 1rem; text-align: center; } .sorter { margin: 1rem auto; display: block; width: min-content; } .sorter button { width: 100%; height: 1.5rem; } ul { position:relative; display: grid; grid-template-columns: repeat(3, calc(33% - 0.5rem)); /*grid-auto-rows: 1fr; ̇*/ grid-gap: 1rem; margin-top: 1.5rem !important; margin-bottom: 1rem !important; padding-left: 1rem !important; padding-right: 0.8rem !important; } .entries li { margin: 0; border: 1.5px solid hsl(60, 20%, 80%); } .entries li > a { background: hsl(60, 20%, 90%); border-bottom: 1px solid hsl(60, 20%, 80%); display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 18px; padding: 0.3rem 0.2rem; text-decoration: none; } .entries li sup a { display: inline; float: right; } li small { display: block; padding: 0.3rem 0.2rem; background: #efefef; } li { position: relative; } li .intro { width: 100%; box-sizing: border-box; position: absolute; height: 0; text-overflow: hidden; overflow: hidden; z-index: 1; border: 1px solid hsl(60, 20%, 80%); background: #efefef; margin: 0; padding: 0; transition: height 1s ease-in-out, padding 0.1s ease-out 1s, z-index 0s ease-out 1s; } li:hover .intro { height: 15rem; z-index: 2; padding: 0.4rem 0.2rem; transition: height 1s ease-in-out, padding 0.1s ease-out 0s, z-index 0s ease-out 0s; } /* Review */ h2 ~ p { text-align: center; } dt { text-align: center; margin-bottom: 0.5rem; margin-top: 1.5rem; } dd { margin: 0; } blockquote { margin: 0; text-align: justify; padding-left: 1rem; padding-right: 1rem; } blockquote br { margin-bottom: 0.5rem; } ` const getIntro = async (url) => { const content = await fetch(url).then(res => res.text()) const parser = new DOMParser() const page = parser.parseFromString(content, "text/html") const quote = page.querySelector("blockquote").innerText.trim().split(" ").filter(e => e) const intro = quote.splice(0, Math.min(50, quote.length)) return intro.join(" ") + "..." } const addIntroLoader = () => { const entryUL = document.querySelector(".entries") const entries = [...entryUL.querySelectorAll("li")] entries.forEach(entry => { const p = document.createElement("p") p.innerText = "Loading..."; p.className = "intro"; entry.appendChild(p) let timeout = null; entry.addEventListener("mouseover", () => { if (entry.className.indexOf("handled") == -1) { const url = entry.querySelector("a").getAttribute("href"); if (localStorage.getItem(url) !== null) { entry.className += " handled" p.innerText = localStorage.getItem(url); } else { timeout = setTimeout(() => { entry.className += " handled" getIntro(url).then(intro => { p.innerText = intro; localStorage.setItem(url, intro); }) }, 500) } } }) entry.addEventListener("mouseout", () => { if (timeout !== null) clearTimeout(timeout) }) }) } const sortType = Object.freeze( { ALPHABETICAL: "Sort alphabetically", DATENEW: "Sort by Date (Newest first)", DATEOLD: "Sort by Date (Oldest first)", COMMENTSMANY: "Sort by Most Comments", COMMENTSFEW: "Sort by Least Comments", } ) const getSort = (elem, type) => { switch (type) { case sortType.ALPHABETICAL: return elem.querySelector("a").innerText; case sortType.DATENEW: return -Number(elem.querySelector("a").getAttribute("href").split("/")[2]); case sortType.DATEOLD: return Number(elem.querySelector("a").getAttribute("href").split("/")[2]); case sortType.COMMENTSMANY: return -Number(elem.querySelector("small").innerText.substring(1).split(" ")[0]); case sortType.COMMENTSFEW: return Number(elem.querySelector("small").innerText.substring(1).split(" ")[0]); } console.warn("Bad sortType!"); } const injectStyle = () => { const styleElem = document.createElement("style"); styleElem.innerText = style; document.head.appendChild(styleElem); } const addSortBar = () => { const page = document.querySelector(".page") const entryUL = document.querySelector(".entries") const entries = [...entryUL.querySelectorAll("li")] let isSorted = false; let isReverse = true; const select = document.createElement("select"); for (let value of Object.values(sortType)) { const option = document.createElement("option"); option.setAttribute("value", value); option.innerText = value; select.appendChild(option); } select.value = window.localStorage.getItem("selected") ?? sortType.DATENEW const button = document.createElement("button") button.textContent = "Sort" button.addEventListener("click", () => { entries.sort((first, second) => { const f = getSort(first, select.value) const s = getSort(second, select.value) if (select.value != sortType.ALPHABETICAL) return Math.sign(f - s) return f.localeCompare(s); }) while (entryUL.firstChild) { entryUL.firstChild.remove() } entries.forEach(e => entryUL.appendChild(e)) window.localStorage.setItem("selected", select.value) }) button.click() const div = document.createElement("div"); div.className = "sorter"; div.appendChild(select); div.appendChild(button); page.insertBefore(div, entryUL) } const fixLinks = () => { const links = document.querySelectorAll("a"); links.forEach(link => { const url = link.getAttribute("href")?.split("/") if (url !== undefined && url[2] == "boards.4chan.org") { url[2] = "desuarchive.org"; // replace quote anchor's p with q for desuarchive.org let anchor = url[url.length-1].split("#"); anchor[1] = "q" + anchor[1].substring(1) url[url.length-1] = anchor.join("#") link.setAttribute("href", url.join("/")) } }) } const makeHeader = () => { const lis = [...document.querySelectorAll(".entries li")]; const length = lis.length const totalReviews = lis.map(e => Number(e.querySelector("small").innerText.split(" ")[0].split("(")[1])).reduce((prev, curr) => prev+curr) document.querySelector("h2").innerHTML+= `
(${length} Fics / ${totalReviews} Reviews)` } (function() { 'use strict'; injectStyle(style); if (window.location.toString().split("/").length == 4) { addSortBar(); addIntroLoader(); makeHeader(); } else { fixLinks() } })();