您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Replaces stat titles and user navigation with icons from https://boxicons.com
当前为
// ==UserScript== // @name Replace stat names with icons // @match https://archiveofourown.org/* // @grant none // @author genusslicht // @description Replaces stat titles and user navigation with icons from https://boxicons.com // @license MIT // @namespace ao3-boxicons // @version 1.0.4 // @icon https://archiveofourown.org/favicon.ico // @supportURL https://gist.github.com/genusslicht/2ba4be62a30f936e7cc9d8f2c33409f5 // ==/UserScript== // AO3 css selectors const WordsTotal = "dl.statistics dd.words"; const WordsWork = "dl.stats dd.words"; const WordsSeries = ".series.meta.group dl.stats>dd:nth-of-type(1)"; 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 BookmarksSeries = ".series.meta.group dl.stats>dd:nth-of-type(4)"; 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 WorksSeries = ".series.meta.group dl.stats>dd:nth-of-type(2)"; const SeriesComplete = ".series.meta.group dl.stats>dd:nth-of-type(3)"; 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/[email protected]/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 {Object} options * @param {Object} options.tooltip adds a tooltip to the element * @param {Object} options.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, options = {}) { const i = document.createElement("i"); i.classList.add("bx"); if (options?.addTooltip && options?.tooltip) i.setAttribute("title", options.tooltip); if (/^bxs?-/i.test(iconClass)) { i.classList.add(iconClass); } else { i.classList.add(options?.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 {Object} options * @param {Object} options.tooltip adds a tooltip to the element * @param {Object} options.solid Indicates if the icon should be of the "solid" variant. * Will be ignored if iconClass has "bx(s)" prefix. */ function setIcon(element, iconClass, options = {}) { if (element.tagName !== "I") element.prepend(getNewIconElement(iconClass, options)); if (options?.tooltip) element.setAttribute("title", options.tooltip); } /** * 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 {Object} options * @param {Object} options.tooltip adds a tooltip to the element * @param {Object} options.solid Indicates if the icon should be of the "solid" variant. * Will be ignored if iconClass has "bx(s)" prefix. */ function findElementsAndSetIcon(querySelector, iconClass, options = {}) { const els = document.querySelectorAll(querySelector); els.forEach((el) => (el.firstChild.nodeType === Node.ELEMENT_NODE ? setIcon(el.firstChild, iconClass, options) : setIcon(el, iconClass, options))); } /** * 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}, ${WordsSeries}`, "pen", { tooltip: "Word Count", solid: true }); findElementsAndSetIcon(ChaptersWork, "food-menu", { tooltip: "Chapters" }); findElementsAndSetIcon(CollectionsWork, "collection", { tooltip: "Collections", solid: true }); findElementsAndSetIcon(CommentsWork, "chat", { tooltip: "Comments", solid: true }); findElementsAndSetIcon(`${KudosTotal}, ${KudosWork}`, "heart", { tooltip: "Kudos", solid: true }); findElementsAndSetIcon(`${BookmarksTotal}, ${BookmarksWork}, ${BookmarksCollection}, ${BookmarksSeries}`, "bookmarks", { tooltip: "Bookmarks", solid: true }); findElementsAndSetIcon(`${HitsTotal}, ${HitsWork}`, "show-alt", { tooltip: "Hits" }); findElementsAndSetIcon(`${SubscribersTotal}, ${SubscribersWork}`, "bell", { tooltip: "Subscriptions", solid: true }); findElementsAndSetIcon(AuthorSubscribers, "bell-ring", { tooltip: "User Subscriptions", solid: true }); findElementsAndSetIcon(CommentThreads, "conversation", { tooltip: "Comment Threads", solid: true }); findElementsAndSetIcon(FandomsCollection, "crown", { tooltip: "Fandoms", solid: true }); findElementsAndSetIcon(`${WorksCollection}, ${WorksSeries}`, "library", { tooltip: "Work Count" }); findElementsAndSetIcon(SeriesComplete, "flag-checkered", { tooltip: "Series Complete", solid: true }); // AO3E elements findElementsAndSetIcon(Kudos2HitsWork, "hot", { tooltip: "Kudos to Hits", solid: true }); findElementsAndSetIcon(ReadingTimeWork, "hourglass", { tooltip: "Time to Read", solid: true }); // calendar icons at works page findElementsAndSetIcon(DatePublishedWork, "calendar-plus", { tooltip: "Published" }); const workStatus = document.querySelector(DateStatusTitle); if (workStatus && workStatus.innerHTML.startsWith("Updated")) { setIcon(document.querySelector(DateStatusWork), "calendar-edit", { tooltip: "Updated" }); } else if (workStatus && workStatus.innerHTML.startsWith("Completed")) { setIcon(document.querySelector(DateStatusWork), "calendar-check", { tooltip: "Completed" }); } } /** * 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", { tooltip: "User Area", addTooltip: true, solid: true })); document.querySelector(PostUserNav).replaceChildren(getNewIconElement("book-add", { tooltip: "New Work", addTooltip: true, solid: true })); document.querySelector(LogoutUserNav).replaceChildren(getNewIconElement("log-out", { tooltip: "Logout", addTooltip: true })); } (function () { initBoxicons(); iconifyStats(); iconifyUserNav(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址