TEXT   1108   0
   740 8.14 KB    356

MLPFicReviews Extras

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

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

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