AO3: [Wrangling] Fandom Assignment Shortcut Groups

Adds groups of shortcuts to assign tags to related fandoms more quickly.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         AO3: [Wrangling] Fandom Assignment Shortcut Groups
// @description  Adds groups of shortcuts to assign tags to related fandoms more quickly.
// @version      1.4

// @author       Nexidava (original by kaerstyne, contributions by raining_kittens, escctrl)
// @namespace    https://greasyfork.org/en/users/725254
// @license      GPL-3.0 <https://www.gnu.org/licenses/gpl.html>

// @match        *://*.archiveofourown.org/tags/*/edit
// @match        *://*.archiveofourown.org/tags/*/wrangle*
// @match        *://*.archiveofourown.org/tags/search*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js
// @grant        none
// ==/UserScript==


// CONFIG

// !! MAKE SURE TO COPY THIS SOMEWHERE SAFE BEFORE UPDATING !!

// configure custom fandom group associations
// these strings should be the canonical names of fandoms
// when a current or suggested fandom matches a set, all fandoms from that set will be inserted at the beginning of the list of fandoms, in the order listed here
// when a current or suggested fandom matches the first subarray of a nested array, all fandoms from the second subarray will be inserted
var fandom_sets = [
    [
        "Fire Emblem: Fuukasetsugetsu | Fire Emblem: Three Houses",
        "Fire Emblem Musou: Fuukasetsugetsu | Fire Emblem Warriors: Three Hopes",
        "Fire Emblem Series",
    ],
    [ // show Hololive and VShojo in VSAC without showing Hololive in VShojo and vice-versa
       [
           "Virtual Streamer Animated Characters"
       ],
       [
           "Hololive (Virtual Streamers)",
           "VShojo (Virtual Streamers)",
       ]
    ],
    [ // show VSAC in Hololive and VShojo without showing Hololive in VShojo and vice-versa
       [
           "Hololive (Virtual Streamers)",
           "VShojo (Virtual Streamers)",
       ],
       [
           "Virtual Streamer Animated Characters"
       ],
    ],
]

// always append these fandoms to the suggested list, regardless of current or tagged fandoms
var always_fandoms = [
    "No Fandom",
    "Original Work",
]

// only display the last pipe of fandoms if true
// show the full fandom names if false
var last_pipe_only = true

// further customize nicknames with canonical:nickname pairs
// these nicknames will appear exactly as written, ignoring last_pipe_only (and can therefore be used to selectively disable it)
var custom_nicknames = {
    "The Locked Tomb Series | Gideon the Ninth Series - Tamsyn Muir": "The Locked Tomb Series",
    "Hourou Musuko | Wandering Son": "Hourou Musuko | Wandering Son",
}

// enable for tag search
var do_tag_search = false

// fandom list for tag search
var tag_search_fandoms = [
    "No Fandom",
]


// CODE

// construct sets
fandom_sets = fandom_sets.map(set => {
    if (Array.isArray(set[0])) {
        return [new Set(set[0]), new Set(set[1])]
    } else {
        let temp = new Set(set)
        return [temp, temp]
    }
})

// helper functions
function encodeTagURL(tag) {
    return "https://archiveofourown.org/tags/" + encodeURI(tag).replace("/", "*s*").replace(".", "*d*").replace("#", "*h*").replace("?", "*q*").replace("&", "*a*") + "/edit";
};

function intersect(a, b) {
    return new Set(Array.from(a).filter(x => b.has(x)));
}

function union(a, b) {
    return new Set([...a, ...b]);
}

function format_fandom(string) {
    return custom_nicknames[string] || (last_pipe_only ? string.split(" | ").slice(-1)[0] : string)
}

function get_fandom_groups(current_fandoms) {
    var new_fandoms = new Set(current_fandoms)
    for (let fandom_set of fandom_sets.reverse()) {
        // if current fandom in a set, add all fandoms from the set
        if (intersect(fandom_set[0], current_fandoms).size !== 0) {
            // place disjoint sets after initial entry, otherwise before
            if (fandom_set[0] === fandom_set[1]) {
                new_fandoms = union(fandom_set[1], new_fandoms);
            } else {
                new_fandoms = union(new_fandoms, fandom_set[1]);
            }
        }
    }
    return union(new_fandoms, always_fandoms)
}


(function($) {

    // which page is this?
    var page_url = window.location.pathname;

    // unwrangled bin
    if (page_url.includes("/wrangle")) {

        // add shortcut checkboxes
        var current_fandom = $("h2.heading a").text();
        var fandom_shortcuts = $('<ul class="filters" style="padding-bottom: .643em;"></ul>');
        var suggested_fandoms = new Set([current_fandom]);

        suggested_fandoms = get_fandom_groups(suggested_fandoms)

        for (let fandom of suggested_fandoms) {
            var escaped_fandom = fandom.replace(/"/g, "&quot;");
            fandom_shortcuts.append('<li style="display: inline-block; margin: 0.1em 0"><label>' +
                                    '<input type="checkbox" name="fandom_shortcut" value="' + escaped_fandom + '">' +
                                    '<span class="indicator" aria-hidden="true"></span>' +
                                    '<span>' + format_fandom(fandom) + '</span>' +
                                    '</label></li>');
        }
        $("dd[title='wrangle to fandom(s)']").prepend(fandom_shortcuts);

        // deselect any previously selected fandoms when selecting a new fandom
        $("input[name='fandom_shortcut']").change(function() {
            $("li.added.tag").remove();
            $("input#fandom_string").val("");
        });

        // add fandom on form submit
        $("form#wrangulator").submit(function() {
            var selected_fandoms = $("input[name='fandom_shortcut']:checked").map((i,v) => v.value).toArray().join(",")
            if (selected_fandoms) {
                var fandom_input = $("input#fandom_string");
                var existing_fandoms = fandom_input.val();
                var separator = existing_fandoms == "" ? "" : ",";
                fandom_input.val(existing_fandoms + separator + selected_fandoms);
            }
        });

    // tag edit page
    } else if (page_url.includes("/edit")) {

        function create_fandom_checkbox(name) {
            const escaped_name = name.replace(/"/g, "&quot;");
            return $("<li></li>").html(`<label>
                                        <input type="checkbox" name="fandom_shortcuts" value="${escaped_name}">
                                        <span class="indicator" aria-hidden="true"></span>
                                        <span><a class="tag" href="${encodeTagURL(name)}">${format_fandom(name)}</a></span>
                                        </label>`);
        }

        // add shortcut checkboxes
        const fandom_list = $("dt:contains('Suggested Fandoms')").next().find("ul");
        fandom_list.addClass("filters");
        var suggested_fandoms = new Set($("dt:contains('Suggested Fandoms')").next().find("li").map(function() {
          return $(this).text();
        }).get());

        suggested_fandoms = get_fandom_groups(suggested_fandoms)
        fandom_list.html(Array.from(suggested_fandoms).map(create_fandom_checkbox));

        // add fandoms on form submit
        $("form#edit_tag").submit(function() {
            var selected_fandoms = $("input[name='fandom_shortcuts']:checked").map(function() {
                return $(this).val();
            }).toArray().join();
            var fandom_input = $("input#tag_fandom_string");
            var existing_fandoms = fandom_input.val();
            var separator = existing_fandoms == "" ? "" : ","
            fandom_input.val(existing_fandoms + separator + selected_fandoms);
        });

    // tag search page
    } else if (page_url.includes("/search") && do_tag_search && tag_search_fandoms) {

        var fandom_tickies = $('<ul class="filters" style="padding-top: .643em;"></ul>');
        for (let fandom of tag_search_fandoms) {
            var escaped_name = fandom.replace(/"/g, "&quot;");
            fandom_tickies.append('<li style="display: inline-block; margin: 0.1em 0;"><label>' +
                                    '<input type="checkbox" name="fandom_shortcut" value="' + escaped_name + '">' +
                                    '<span class="indicator" aria-hidden="true"></span>' +
                                    '<span>' + fandom + '</span>' +
                                    '</label></li>');
        }
        $("#tag_search_fandoms_autocomplete").parent().parent().prepend(fandom_tickies);

        // deselect any previously selected fandoms when selecting a new fandom
        $("input[name='fandom_shortcut']").change(function() {
            $("li.added.tag").remove();
        });

        // add fandom on form submit
        $("form#new_tag_search").submit(function() {
            var search_fandoms = $("input[name='fandom_shortcut']:checked").map((i,e) => e.value).toArray()
            // get existing fandom filters from added tags since input#tag_search_fandoms doesn't reflect removed tags
            $("li.added.tag").contents(":not(span)").map((i,e)=>search_fandoms.push(e.data.trim()))
            // filter empty values and duplicates
            search_fandoms = [...new Set(search_fandoms)].filter(e => e)
            $("input#tag_search_fandoms").val(search_fandoms.join(","));
        });
   }

})(jQuery);