AO3: [Wrangling] Find the Tag in the Blurb

Make the tag of the page you're on (and up to 300 synonymous/meta/sub/parent/child tags) more visually distinct

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        AO3: [Wrangling] Find the Tag in the Blurb
// @namespace   https://github.com/RhineCloud
// @version     1.2
// @description Make the tag of the page you're on (and up to 300 synonymous/meta/sub/parent/child tags) more visually distinct
// @grant       none
// @author      Rhine
// @include     /^https?:\/\/[^\/]*archiveofourown.org\/(works|bookmarks)\?.*tag_id=.+/
// @include     /^https?:\/\/[^\/]*archiveofourown.org\/tags\/[^\/]+(\/((works|bookmarks).*)?)?$/
// @exclude     /^https?:\/\/[^\/]*archiveofourown.org\/tags\/[^\/]+\/(edit|wrangle|comments|troubleshooting).*/
// @license     GPL-3.0 <https://www.gnu.org/licenses/gpl.html>
// ==/UserScript==


// CSS SETTINGS
// DO NOT change the first string in each line
// adjust the second string in each line as you like, any valid CSS works
// delete the entire line if you are not ever at all interested in having a specific kind of related tag highlighted
const STYLE_MAP = new Map([
    ["", "color: #fff; background: #900;"],
    ["synonym", "color: #fff; background: rgba(153, 0, 0, 75%);"],
    ["meta", "color: #fff; text-shadow: 0 0 5px #900;"],
    ["sub", "color: #fff; background: #c00;"],
    ["parent", "color: #fff; text-shadow: 0 0 5px #099; font-style: oblique;"],
    ["child", "color: #fff; background: #090; font-weight: bold;"]
]);

// CACHE SETTINGS
// set these to either true or false
// automatically cache related tags
const AUTO_CACHE_RELATED = false
// show the "Cache related tags" button
const SHOW_CACHE_BUTTON = true


// MAIN FUNCTION
// find matching tags in blurbs and add styling
const PATHNAME = window.location.pathname;
let tag_id = PATHNAME.startsWith("/tags/") ? PATHNAME.split("/")[2] :
    document.querySelector("#main h2.heading a.tag").getAttribute("href").split("/")[2];
style_tag(tag_id, STYLE_MAP.get(""));

if (1 < STYLE_MAP.size) {
    // find corresponding tag canonical if on tags landing page of a synonym
    if (document.querySelector("div.merger.module")) {
        tag_id = document.querySelector("div.merger.module a.tag").getAttribute("href").split("/")[2];
    }

    // automatically cache related tags if applicable
    if (AUTO_CACHE_RELATED && sessionStorage.getItem("cached_tag") !== tag_id) {
        cache_related_tags().then(() => {});
    }

    // find further related tags if applicable
    const button = document.createElement("li");
    if (sessionStorage.getItem("cached_tag") === tag_id) {
        button.innerHTML = `<span class="current">Cached related tags!</span>`;
        style_related_tags();
    } else {
        button.setAttribute("id", "cache_related_tags");
        button.innerHTML = "<a>Cache related tags</a>";
        button.addEventListener("click", cache_related_tags);
    }
    if (SHOW_CACHE_BUTTON) {
        document.querySelector("#main ul.navigation.actions").prepend(button);
    }
}


// HELPER FUNCTIONS
// apply styling to appearances of a specific tag in all blurbs
function style_tag(tag_id, style) {
    document.querySelectorAll(`li.blurb a.tag[href="/tags/${tag_id}/works"], li.blurb a.tag[href="/tags/${tag_id}/bookmarks"]`)
        .forEach(tag => tag.setAttribute("style", style));
}

// use sessionStorage to effectively cache info on related tags (per window/tab)
// and apply styling on related tags once they're cached
async function cache_related_tags() {
    const tag_home = 3 === PATHNAME.split("/").length ? document.querySelector("#main div.tag.home") :
        await fetch(`/tags/${tag_id}`).then(response => response.text()).then(html => {
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, "text/html");
            return doc.querySelector("#main div.tag.home");
        });

    for (let type of STYLE_MAP.keys()) {
        if (type) {
            cache_relation(type, tag_home);
        }
    }
    sessionStorage.setItem("cached_tag", tag_id);

    if (SHOW_CACHE_BUTTON) {
        document.querySelector("#cache_related_tags").innerHTML = `<span class="current">Cached related tags!</span>`;
        document.querySelector("#cache_related_tags").removeEventListener("click", cache_related_tags);
    }
    style_related_tags();
}

// cache one type of relation in sessionStorage
function cache_relation(type, tag_home) {
    let tags = tag_home.querySelectorAll(`div.${type} a.tag`);
    tags = nodes_to_strings(tags);
    sessionStorage.setItem(`cached_${type}_tags`, tags.join(","));
}

// transform list of nodes (linked tags from the page) into plain strings for sessionStorage
function nodes_to_strings(node_list) {
    const strings = [];
    for (let node of node_list.values()) {
        strings.push(node.getAttribute("href").split("/")[2]);
    }
    return strings;
}

// apply styling to all tags of all relation types in all blurbs
function style_related_tags() {
    for (let type of STYLE_MAP.keys()) {
        if (type) {
            sessionStorage.getItem(`cached_${type}_tags`).split(",")
                .forEach(tag => style_tag(tag, STYLE_MAP.get(type)));
        }
    }
}