TEXT   908   0
   740 8.14 KB    356

MLPFicReviews Extras

By Fillyanon
Created: 2023-02-07 18:46:57
Expiry: Never

  1. // ==UserScript==
  2. // @name MLPFicReviews Extras
  3. // @version 0.2
  4. // @description Adds a few handy functions to mlpficreviews.org.uk
  5. // @author Fillyanon
  6. // @grant none
  7. // ==/UserScript==
  8.  
  9.  
  10. const style = `
  11. body {
  12. background-attachment: fixed;
  13. background-repeat: no-repeat;
  14. background-size: 100%;
  15. }
  16.  
  17. .page {
  18. width: 1100px;
  19. background: #eef2ff;
  20. padding: 0;
  21. }
  22.  
  23. h1 {
  24. text-align: center;
  25. font-size: 2.5rem;
  26. padding: 1rem;
  27. background: hsl(60, 60%, 90%);
  28. }
  29.  
  30. h1::before {
  31. display: block;
  32. }
  33.  
  34. a {
  35. text-decoration: none;
  36. }
  37.  
  38. h2 {
  39. margin-top: 1rem;
  40. text-align: center;
  41. }
  42.  
  43. .sorter {
  44. margin: 1rem auto;
  45. display: block;
  46. width: min-content;
  47. }
  48.  
  49. .sorter button {
  50. width: 100%;
  51. height: 1.5rem;
  52. }
  53.  
  54. ul {
  55. position:relative;
  56. display: grid;
  57. grid-template-columns: repeat(3, calc(33% - 0.5rem));
  58. /*grid-auto-rows: 1fr; ̇*/
  59. grid-gap: 1rem;
  60. margin-top: 1.5rem !important;
  61. margin-bottom: 1rem !important;
  62. padding-left: 1rem !important;
  63. padding-right: 0.8rem !important;
  64. }
  65.  
  66. .entries li {
  67. margin: 0;
  68. border: 1.5px solid hsl(60, 20%, 80%);
  69. }
  70.  
  71. .entries li > a {
  72. background: hsl(60, 20%, 90%);
  73. border-bottom: 1px solid hsl(60, 20%, 80%);
  74. display: block;
  75. overflow: hidden;
  76. text-overflow: ellipsis;
  77. white-space: nowrap;
  78. font-size: 18px;
  79. padding: 0.3rem 0.2rem;
  80.  
  81. text-decoration: none;
  82. }
  83.  
  84. .entries li sup a {
  85. display: inline;
  86. float: right;
  87. }
  88.  
  89. li small {
  90. display: block;
  91. padding: 0.3rem 0.2rem;
  92. background: #efefef;
  93. }
  94.  
  95. li {
  96. position: relative;
  97. }
  98.  
  99. li .intro {
  100. width: 100%;
  101. box-sizing: border-box;
  102. position: absolute;
  103. height: 0;
  104. text-overflow: hidden;
  105. overflow: hidden;
  106. z-index: 1;
  107. border: 1px solid hsl(60, 20%, 80%);
  108.  
  109. background: #efefef;
  110.  
  111. margin: 0;
  112. padding: 0;
  113.  
  114. transition: height 1s ease-in-out, padding 0.1s ease-out 1s, z-index 0s ease-out 1s;
  115. }
  116.  
  117. li:hover .intro
  118. {
  119. height: 15rem;
  120. z-index: 2;
  121.  
  122. padding: 0.4rem 0.2rem;
  123.  
  124. transition: height 1s ease-in-out, padding 0.1s ease-out 0s, z-index 0s ease-out 0s;
  125. }
  126.  
  127. /* Review */
  128.  
  129. h2 ~ p {
  130. text-align: center;
  131. }
  132.  
  133. dt {
  134. text-align: center;
  135. margin-bottom: 0.5rem;
  136. margin-top: 1.5rem;
  137. }
  138.  
  139. dd {
  140. margin: 0;
  141. }
  142.  
  143. blockquote {
  144. margin: 0;
  145. text-align: justify;
  146. padding-left: 1rem;
  147. padding-right: 1rem;
  148. }
  149.  
  150. blockquote br {
  151. margin-bottom: 0.5rem;
  152. }
  153. `
  154.  
  155. const getIntro = async (url) =>
  156. {
  157. const content = await fetch(url).then(res => res.text())
  158. const parser = new DOMParser()
  159. const page = parser.parseFromString(content, "text/html")
  160.  
  161. const quote = page.querySelector("blockquote").innerText.trim().split(" ").filter(e => e)
  162. const intro = quote.splice(0, Math.min(50, quote.length))
  163.  
  164. return intro.join(" ") + "..."
  165. }
  166.  
  167. const addIntroLoader = () =>
  168. {
  169. const entryUL = document.querySelector(".entries")
  170. const entries = [...entryUL.querySelectorAll("li")]
  171.  
  172. entries.forEach(entry =>
  173. {
  174. const p = document.createElement("p")
  175. p.innerText = "Loading...";
  176. p.className = "intro";
  177. entry.appendChild(p)
  178.  
  179. let timeout = null;
  180.  
  181. entry.addEventListener("mouseover", () =>
  182. {
  183. if (entry.className.indexOf("handled") == -1)
  184. {
  185. const url = entry.querySelector("a").getAttribute("href");
  186.  
  187. if (localStorage.getItem(url) !== null)
  188. {
  189. entry.className += " handled"
  190. p.innerText = localStorage.getItem(url);
  191. }
  192. else
  193. {
  194. timeout = setTimeout(() => {
  195. entry.className += " handled"
  196. getIntro(url).then(intro => {
  197. p.innerText = intro;
  198. localStorage.setItem(url, intro);
  199. })
  200. }, 500)
  201. }
  202. }
  203. })
  204.  
  205. entry.addEventListener("mouseout", () =>
  206. {
  207. if (timeout !== null) clearTimeout(timeout)
  208. })
  209. })
  210. }
  211.  
  212. const sortType = Object.freeze(
  213. {
  214. ALPHABETICAL: "Sort alphabetically",
  215. DATENEW: "Sort by Date (Newest first)",
  216. DATEOLD: "Sort by Date (Oldest first)",
  217. COMMENTSMANY: "Sort by Most Comments",
  218. COMMENTSFEW: "Sort by Least Comments",
  219. }
  220. )
  221.  
  222. const getSort = (elem, type) =>
  223. {
  224. switch (type)
  225. {
  226. case sortType.ALPHABETICAL:
  227. return elem.querySelector("a").innerText;
  228.  
  229. case sortType.DATENEW:
  230. return -Number(elem.querySelector("a").getAttribute("href").split("/")[2]);
  231.  
  232. case sortType.DATEOLD:
  233. return Number(elem.querySelector("a").getAttribute("href").split("/")[2]);
  234.  
  235. case sortType.COMMENTSMANY:
  236. return -Number(elem.querySelector("small").innerText.substring(1).split(" ")[0]);
  237.  
  238. case sortType.COMMENTSFEW:
  239. return Number(elem.querySelector("small").innerText.substring(1).split(" ")[0]);
  240. }
  241.  
  242. console.warn("Bad sortType!");
  243. }
  244.  
  245. const injectStyle = () =>
  246. {
  247. const styleElem = document.createElement("style");
  248. styleElem.innerText = style;
  249. document.head.appendChild(styleElem);
  250. }
  251.  
  252. const addSortBar = () =>
  253. {
  254. const page = document.querySelector(".page")
  255. const entryUL = document.querySelector(".entries")
  256. const entries = [...entryUL.querySelectorAll("li")]
  257.  
  258. let isSorted = false;
  259. let isReverse = true;
  260.  
  261. const select = document.createElement("select");
  262.  
  263. for (let value of Object.values(sortType))
  264. {
  265. const option = document.createElement("option");
  266. option.setAttribute("value", value);
  267. option.innerText = value;
  268. select.appendChild(option);
  269. }
  270.  
  271. select.value = window.localStorage.getItem("selected") ?? sortType.DATENEW
  272.  
  273. const button = document.createElement("button")
  274. button.textContent = "Sort"
  275.  
  276. button.addEventListener("click", () =>
  277. {
  278. entries.sort((first, second) => {
  279. const f = getSort(first, select.value)
  280. const s = getSort(second, select.value)
  281.  
  282. if (select.value != sortType.ALPHABETICAL)
  283. return Math.sign(f - s)
  284.  
  285. return f.localeCompare(s);
  286. })
  287.  
  288. while (entryUL.firstChild)
  289. {
  290. entryUL.firstChild.remove()
  291. }
  292.  
  293. entries.forEach(e => entryUL.appendChild(e))
  294. window.localStorage.setItem("selected", select.value)
  295. })
  296.  
  297. button.click()
  298.  
  299. const div = document.createElement("div");
  300. div.className = "sorter";
  301.  
  302. div.appendChild(select);
  303. div.appendChild(button);
  304.  
  305. page.insertBefore(div, entryUL)
  306. }
  307.  
  308. const fixLinks = () =>
  309. {
  310. const links = document.querySelectorAll("a");
  311.  
  312. links.forEach(link => {
  313. const url = link.getAttribute("href")?.split("/")
  314. if (url !== undefined && url[2] == "boards.4chan.org")
  315. {
  316. url[2] = "desuarchive.org";
  317.  
  318. // replace quote anchor's p with q for desuarchive.org
  319. let anchor = url[url.length-1].split("#");
  320. anchor[1] = "q" + anchor[1].substring(1)
  321. url[url.length-1] = anchor.join("#")
  322.  
  323. link.setAttribute("href", url.join("/"))
  324. }
  325. })
  326. }
  327.  
  328. const makeHeader = () =>
  329. {
  330. const lis = [...document.querySelectorAll(".entries li")];
  331. const length = lis.length
  332. const totalReviews = lis.map(e => Number(e.querySelector("small").innerText.split(" ")[0].split("(")[1])).reduce((prev, curr) => prev+curr)
  333.  
  334. document.querySelector("h2").innerHTML+= `<br><small>(${length} Fics / ${totalReviews} Reviews)</small>`
  335. }
  336.  
  337. (function() {
  338. 'use strict';
  339.  
  340. injectStyle(style);
  341.  
  342. if (window.location.toString().split("/").length == 4)
  343. {
  344. addSortBar();
  345. addIntroLoader();
  346. makeHeader();
  347. }
  348. else
  349. {
  350. fixLinks()
  351. }
  352. })();

Fillyanon's Bookshelf

by Fillyanon

Thus passes threadly glory

by Fillyanon

Flag Randomizer Userscript

by Fillyanon

Post Previewer UserScript

by Fillyanon

MLPFicReviews Extras

by Fillyanon