Last active
April 7, 2024 15:46
-
-
Save genusslicht/2ba4be62a30f936e7cc9d8f2c33409f5 to your computer and use it in GitHub Desktop.
Userscript for Violentmonkey or other browser add-ons. Literally just replaces the stats labels with icons. GreasyFork install location: https://greasyfork.org/de/scripts/490214-replace-stat-names-with-icons
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Iconify Stats with Boxicons | |
// @match https://archiveofourown.org/* | |
// @grant none | |
// @author genusslicht | |
// @description Replaces Stats and user navigation with icons from https://boxicons.com | |
// @license MIT | |
// @namespace ao3-boxicons | |
// @version 1.0.0 | |
// @icon https://archiveofourown.org/favicon.ico | |
// ==/UserScript== | |
// AO3 css selectors | |
const WordsTotal = "dl.statistics dd.words"; | |
const WordsWork = "dl.stats dd.words"; | |
const ChaptersWork = "dl.stats dd.chapters"; | |
const CollectionsWork = "dl.stats dd.collections"; | |
const CommentsWork = "dl.stats dd.comments"; | |
const KudosTotal = "dl.statistics dd.kudos"; | |
const KudosWork = "dl.stats dd.kudos"; | |
const BookmarksTotal = "dl.statistics dd.bookmarks"; | |
const BookmarksWork = "dl.stats dd.bookmarks"; | |
const BookmarksCollection = "li.collection dl.stats dd a[href$=bookmarks]"; | |
const HitsTotal = "dl.statistics dd.hits"; | |
const HitsWork = "dl.stats dd.hits"; | |
const SubscribersTotal = "dl.statistics dd[class=subscriptions]"; | |
const SubscribersWork = "dl.stats dd.subscriptions"; | |
const FandomsCollection = "li.collection dl.stats dd a[href$=fandoms]"; | |
const AuthorSubscribers = "dl.statistics dd.user.subscriptions"; | |
const CommentThreads = "dl.statistics dd.comment.thread"; | |
const WorksCollection = "li.collection dl.stats dd a[href$=works]"; | |
const Kudos2HitsWork = "dl.stats dd.kudos-hits-ratio"; | |
const ReadingTimeWork = "dl.stats dd.reading-time"; | |
const DatePublishedWork = "dl.work dl.stats dd.published"; | |
const DateStatusTitle = "dl.work dl.stats dt.status"; | |
const DateStatusWork = "dl.work dl.stats dd.status"; | |
const AccountUserNav = "#header a.dropdown-toggle[href*='/users/']"; | |
const PostUserNav = "#header a.dropdown-toggle[href*='/works/new']"; | |
const LogoutUserNav = "#header a[href*='/users/logout']"; | |
/** | |
* Initialises boxicons.com css and adds a small css to add some space between icon and stats count. | |
*/ | |
function initBoxicons() { | |
// load boxicon style | |
const boxicons = document.createElement("link"); | |
boxicons.setAttribute("href", "https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css"); | |
boxicons.setAttribute("rel", "stylesheet"); | |
document.head.appendChild(boxicons); | |
// css that adds margin for icons | |
const boxiconsCSS = document.createElement("style"); | |
boxiconsCSS.setAttribute("type", "text/css"); | |
boxiconsCSS.innerHTML = ` | |
i.bx { | |
margin-right: .3em; | |
}`; | |
document.head.appendChild(boxiconsCSS); | |
} | |
/** | |
* Creates a new element with the icon class added to the classList. | |
* | |
* @param {string} iconClass Name of the boxicons class to use. (The "bx(s)" prefix can be omitted) | |
* @param {boolean} solid Indicates if the icon should be of the "solid" variant. | |
* Will be ignored if iconClass has "bx(s)" prefix. | |
* @returns <i> Element with the neccessary classes for a boxicons icon. | |
*/ | |
function getNewIconElement(iconClass, solid = false) { | |
const i = document.createElement("i"); | |
i.classList.add("bx"); | |
if (/^bxs?-/i.test(iconClass)) | |
i.classList.add(iconClass); | |
else { | |
i.classList.add(solid ? "bxs-"+iconClass : "bx-"+iconClass); | |
} | |
return i; | |
} | |
/** | |
* Prepends the given boxicons class to the given element. | |
* Note: If the element is an <i> tag, nothing will happen, as we assume that the <i> is already an icon. | |
* | |
* @param {HTMLElement} element parent element that the icon class should be prepended to. | |
* @param {string} iconClass name of the boxicons class to use. (The "bx(s)" prefix can be omitted) | |
* @param {boolean} solid Indicates if the icon should be of the "solid" variant. | |
* Will be ignored if iconClass has "bx(s)" prefix. | |
*/ | |
function setIcon(element, iconClass, solid = false) { | |
if (element.tagName !== "I") element.prepend(getNewIconElement(iconClass, solid)); | |
} | |
/** | |
* Iterates through all elements that apply to the given querySelector and adds an element with the given icon class to it. | |
* | |
* @param {string} querySelector CSS selector for the elements to find and iconify. | |
* @param {string} iconClass name of the boxicons class to use. (The "bx(s)" prefix can be omitted) | |
* @param {boolean} solid Indicates if the icon should be of the "solid" variant. | |
* Will be ignored if iconClass has "bx(s)" prefix. | |
*/ | |
function findElementsAndSetIcon(querySelector, iconClass, solid = false) { | |
const els = document.querySelectorAll(querySelector); | |
els.forEach(el => el.firstChild.nodeType === Node.ELEMENT_NODE ? setIcon(el.firstChild, iconClass, solid) : setIcon(el, iconClass, solid)); | |
} | |
/** | |
* Adds an CSS that will hide the stats titles and prepends an icon to all stats. | |
*/ | |
function iconifyStats() { | |
// css to hide stats titles | |
const statsCSS = document.createElement("style"); | |
statsCSS.setAttribute("type", "text/css"); | |
statsCSS.innerHTML = ` | |
dl.stats dt { | |
display: none !important; | |
}`; | |
document.head.appendChild(statsCSS); | |
findElementsAndSetIcon(`${WordsTotal}, ${WordsWork}`, "pen", true); | |
findElementsAndSetIcon(ChaptersWork, "food-menu"); | |
findElementsAndSetIcon(CollectionsWork, "collection"); | |
findElementsAndSetIcon(CommentsWork, "chat", true); | |
findElementsAndSetIcon(`${KudosTotal}, ${KudosWork}`, "heart", true); | |
findElementsAndSetIcon(`${BookmarksTotal}, ${BookmarksWork}, ${BookmarksCollection}`, "bookmarks", true); | |
findElementsAndSetIcon(`${HitsTotal}, ${HitsWork}`, "show-alt"); | |
findElementsAndSetIcon(`${SubscribersTotal}, ${SubscribersWork}`, "bell", true); | |
findElementsAndSetIcon(AuthorSubscribers, "bell-ring", true); | |
findElementsAndSetIcon(CommentThreads, "conversation", true); | |
findElementsAndSetIcon(FandomsCollection, "crown", true); | |
findElementsAndSetIcon(WorksCollection, "library"); | |
// AO3E elements | |
findElementsAndSetIcon(Kudos2HitsWork, "hot", true); | |
findElementsAndSetIcon(ReadingTimeWork, "hourglass", true); | |
// calendar icons at works page | |
findElementsAndSetIcon(DatePublishedWork, "calendar-plus"); | |
const workStatus = document.querySelector(DateStatusTitle); | |
if (workStatus && workStatus.innerHTML.startsWith("Updated")) { | |
setIcon(document.querySelector(DateStatusWork), "calendar-edit"); | |
} else if (workStatus && workStatus.innerHTML.startsWith("Completed")) { | |
setIcon(document.querySelector(DateStatusWork), "calendar-check"); | |
} | |
} | |
/** | |
* Replaces the "Hi, {user}!", "Post" and "Log out" text at the top of the page with icons. | |
*/ | |
function iconifyUserNav() { | |
// add css for user navigation icons | |
const userNavCss = document.createElement("style"); | |
userNavCss.setAttribute("type", "text/css"); | |
userNavCss.innerHTML = ` | |
${LogoutUserNav}, | |
${AccountUserNav}, | |
${PostUserNav} { | |
/* font size needs to be higher to make icons the right size */ | |
font-size: 1.25rem; | |
/* left and right padding for a slightly bigger hover hitbox */ | |
padding: 0 .3rem; | |
} | |
${LogoutUserNav} i.bx { | |
/* overwrite the right margin for logout icon */ | |
margin-right: 0; | |
/* add left margin instead to add more space to user actions */ | |
margin-left: .3em; | |
}`; | |
document.head.appendChild(userNavCss); | |
// replace text with icons | |
document.querySelector(AccountUserNav).replaceChildren(getNewIconElement("user-circle", true)); | |
document.querySelector(PostUserNav).replaceChildren(getNewIconElement("book-add", true)); | |
document.querySelector(LogoutUserNav).replaceChildren(getNewIconElement("log-out")); | |
} | |
(function() { | |
initBoxicons(); | |
iconifyStats(); | |
iconifyUserNav(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment