AO3: [Wrangling] Synonym Autofill

Adds buttons to relationship edit tag pages to autofill synonym field from tagged characters

// ==UserScript==
// @name         AO3: [Wrangling] Synonym Autofill
// @description  Adds buttons to relationship edit tag pages to autofill synonym field from tagged characters
// @version      1.0

// @author       Nexidava
// @namespace    https://greasyfork.org/en/users/725254

// @match        *://*.archiveofourown.org/tags/*/edit
// @require      https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js
// @grant        none
// @license      GPL-3.0 <https://www.gnu.org/licenses/gpl.html>
// ==/UserScript==

class Tag {
    constructor(tag, western) {
        this.tag = tag
        const match = tag.match(/(.*?) ?(\(.*\))?\s?$/)
        this.name = match[1]
        this.disambig = match[2]
        this.western = western
        this.order = this.getOrder()
    }
    getOrder() {
        var order = this.name
        if (this.western) {
            // put the last name first.  this will unavoidably break with multiple space-separated last names, but does handle middle names
            order = order.split(" | ").map(name => name.split(" ")).map(words => words.slice(-1).concat(words.slice(0,-1)).join(" ")).join(" | ")
        }
        if (this.disambig) {
            order += " " + this.disambig
        }
        return order
    }
}

function autofill($, western) {
    // get character tags
    const charbox = $("dd[title=Characters].tags.listbox.group")
    const taglist = charbox.find("a.tag, li.added.tag")
    const charobj = taglist.map(function(t) { return $(this).contents().filter(function() { return this.nodeType === 3; }).text().trim() })
    var charr = $.makeArray(charobj)
    const name = $("input#tag_name").val()

    // if no chars tagged, get characters from tag
    if (!charr.length) {
        charr = name.split(/\s?[/&]\s?/g)
    }

    // construct and sort char objects
    var chars = charr.map(x => new Tag(x, western))
    chars.sort((a, b) => a.order.localeCompare(b.order))

    // get rel type from tag name
    var sep
    if (name.match(/\//g)) { sep = "/" }
    else if (name.match(/&/g)) { sep = " & " }
    else { sep = " # " ; console.log("Unable to intuit correct rel type, using #") }

    // construct rel tag
    const hasdis = chars.map(x => x.disambig).filter(Boolean)
    const ndis = (new Set(hasdis)).size
    const keep_disambig = $("input[id='keep_disambig']:checked").length === 1
    var tag

    if (ndis == 1 && !keep_disambig) {
        tag = chars.map(x => x.name).join(sep)

        if (hasdis.length == chars.length) {
            tag += " " + chars[0].disambig
        }
    }
    else {
        tag = chars.map(x => x.tag).join(sep)
    }

    var canonical_checkbox = $("input#tag_canonical")
    var syn_autocomplete = $("input#tag_syn_string_autocomplete")

    // if computed tag matches existing name, tick canonical box instead of autofilling
    if (tag === name) {
        canonical_checkbox.prop('checked', true)
        syn_autocomplete.val("")
    }
    // else input rel tag to synonym field and uncheck canonical checkbox if needed
    else {
        canonical_checkbox.prop('checked', false)
        syn_autocomplete.val(tag)
    }
}

// set up buttons
(function($) {
    // only function on relationship tags
    if ($("strong:contains('Relationship')").add("option:selected:contains('Relationship')").length == 0) { return }

    const label = $('<dt><label for="tag_syn_autofill">Autofill Synonym</label></dt>')
    const gf = $('<li><a href="#" id="autofill-gf">Given Family (Western)</a></li>')
    const fg = $('<li><a href="#" id="autofill-fg">Family Given (Eastern)</a></li>')
    const clear = $('<li><a href="#" id="autofill-clear">Clear</a></li>')
    const buttons = $('<ul class="actions" role="menu" style="float: left"></ul>')
    const disambig = $('<ul class="tags commas filters actions" role="menu" style="float: left"><li><label draggable="true" style="cursor: pointer; margin: 0.375em auto; float: left"><span>Disambiguate </span><input type="checkbox" value="1" id="keep_disambig"><span class="indicator" id="kdi" aria-hidden="true"></span></label></li></ul>')
    document.styleSheets[0].addRule('#kdi:before','border: 0; margin-right: 0;')
    const dd = $('<dd></dd>')
    const prev = $("input#tag_syn_string").parent()

    gf.click(x => autofill($, true))
    fg.click(x => autofill($, false))
    clear.click(x => { $("input#tag_syn_string_autocomplete").val(""); $("input#tag_canonical").prop("checked", false) })
    buttons.append(gf, fg, clear)
    dd.append(buttons)
    buttons.after(disambig)
    prev.after(label, dd)

})(jQuery);