AO3: Replace Y/N in works with your name

replaces Y/N and other placeholders in x Reader fic with the name of your choice

Stan na 16-10-2023. Zobacz najnowsza wersja.

// ==UserScript==
// @name         AO3: Replace Y/N in works with your name
// @description  replaces Y/N and other placeholders in x Reader fic with the name of your choice
// @author       escctrl
// @namespace    https://greasyfork.org/en/users/906106-escctrl
// @version      0.3
// @match        https://archiveofourown.org/works/*
// @license      MIT
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js
// @require      https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js
// @grant        none
// ==/UserScript==

var given_name = "", family_name = "";

// the function to deal with all the configuration - using jQueryUI for dialogs
(function($) {
    'use strict';

    // retrieve localStorage on page load
    if (!localStorage) {
        console.log("The userscript \"AO3: Replace Y/N in works with your name\" terminated early because local storage cannot be accessed");
        return false;
    }
    else loadconfig();

    // if no other script has created it yet, write out a "Userscripts" option to the main navigation
    if ($('#scriptconfig').length == 0) {
        $('#header ul.primary.navigation li.dropdown').last()
            .after(`<li class="dropdown" id="scriptconfig">
                <a class="dropdown-toggle" href="/" data-toggle="dropdown" data-target="#">Userscripts</a>
                <ul class="menu dropdown-menu"></ul>
            </li>`);
    }
    // then add this script's config option to navigation dropdown
    $('#scriptconfig .dropdown-menu').append(`<li><a href="javascript:void(0);" id="opencfg_replaceYN">Replace Y/N</a></li>`);

    // if the background is dark, use the dark UI theme to match
    let dialogtheme = lightOrDark($('body').css('background-color')) == "dark" ? "ui-darkness" : "base";

    // adding the jQuery stylesheet to style the dialog, and fixing the interference of AO3's styling
    $("head").append(`<link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/${dialogtheme}/jquery-ui.css">`)
    .append(`<style tyle="text/css">
    #cfgdialog_replaceYN legend {font-size: inherit; height: auto; width: auto; opacity: inherit;}
    #cfgdialog_replaceYN form {box-shadow: revert; cursor:auto;}
    #cfgdialog_replaceYN fieldset {background: revert; box-shadow: revert;}
    #cfgdialog_replaceYN input[type='text'] { position: relative; top: 1px; padding: .4em; width: 95%; }
    #cfgdialog_replaceYN fieldset p { padding-top: 0; padding-left: 0; padding-right: 0; }
    </style>`);

    // the config dialog container
    let cfg = document.createElement('div');
    cfg.id = 'cfgdialog_replaceYN';
    $(cfg).html(`<form>
    <fieldset><legend>Given Name</legend>
        <p>replaces Y/N and G/N in the work text</p>
        <input type="text" name="given" id="given" value="${given_name}" placeholder="your given name">
    </fieldset>
    <fieldset><legend>Family Name</legend>
        <p>replaces F/N, L/N and Y/L/N in the work text</p>
        <input type="text" name="family" id="family" value="${family_name}" placeholder="your family name">
    </fieldset>
    <p>Any changes to this configuration will only take effect after reloading the page!</p>
    <!-- Allow form submission with keyboard without duplicating the dialog button -->
    <input type="submit" tabindex="-1" style="display: none;">
    </form>`);

    // attach it to the DOM so that selections work
    $("body").append(cfg);

    let dialogwidth = parseInt($("body").css("width")); // parseInt ignores letters (px)
    dialogwidth = dialogwidth > 400 ? 400 : dialogwidth * 0.9;

    // initialize the dialog (but don't open it)
    $( "#cfgdialog_replaceYN" ).dialog({
        appendTo: "#main",
        modal: true,
        title: 'Replace Y/N Configuration',
        draggable: true,
        resizable: false,
        autoOpen: false,
        width: dialogwidth,
        position: {my:"center", at: "center top"},
        buttons: {
            Reset: deleteconfig,
            Save: setconfig,
            Cancel: closedialog
        }
    });

    function closedialog() {
        $( "#cfgdialog_replaceYN" ).dialog( "close" );
    }

    // on click of the menu, open the configuration dialog
    $("#opencfg_replaceYN").on("click", function(e) {
        $( "#cfgdialog_replaceYN" ).dialog('open');
    });

    // event triggers if form is submitted with the <enter> key
    $( "#cfgdialog_replaceYN form" ).on("submit", (e)=>{
        e.preventDefault();
        setconfig();
    });

    // functions to deal with the localStorage
    function loadconfig() {
        given_name = localStorage.getItem('script-replaceYN-given') || "";
        family_name = localStorage.getItem('script-replaceYN-family') || "";
    }
    function setconfig() {
        // grab form fields for easier selection later
        let new_given = $(`#cfgdialog_replaceYN form [name="given"]`).val();
        let new_family = $(`#cfgdialog_replaceYN form [name="family"]`).val();
        localStorage.setItem('script-replaceYN-given', new_given);
        localStorage.setItem('script-replaceYN-family', new_family);
        closedialog();
    }
    function deleteconfig() {
        // empties all fields in the form
        $('#cfgdialog_replaceYN form [name]').val("");
        // delete the localStorage
        localStorage.removeItem('script-replaceYN-given');
        localStorage.removeItem('script-replaceYN-family');
        closedialog();
    }

})(jQuery);

// function to run the text replacement on Y/N and [Y/]L/N etc
// sadly this can run only on initial page load - after that the work text has been changed and we wouldn't find the placeholders to replace
function replaceYN() {
    // don't run a replace if no name has been configured
    if (given_name != "" || family_name != "") {
        document.querySelectorAll('#main #chapters .userstuff > *').forEach((p) => {
            p.innerHTML = p.innerHTML
                .replace(/\(Y\/N\)|Y\/N/ig, given_name).replace(/\(G\/N\)|G\/N/ig, given_name).replace(/\(F\/N\)|F\/N/ig, given_name)
                .replace(/\((Y\/)?L\/N\)|(Y\/)?L\/N/ig, family_name);
        });
    }
}

// replace text only when page finished loading
if (document.readyState === 'complete') replaceYN();
else window.addEventListener('load', () => replaceYN());

// helper function to determine whether a color (the background in use) is light or dark
// https://awik.io/determine-color-bright-dark-using-javascript/
function lightOrDark(color) {

    // Variables for red, green, blue values
    var r, g, b, hsp;

    // Check the format of the color, HEX or RGB?
    if (color.match(/^rgb/)) {
        // If RGB --> store the red, green, blue values in separate variables
        color = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/);
        r = color[1];
        g = color[2];
        b = color[3];
    }
    else {
        // If hex --> Convert it to RGB: http://gist.github.com/983661
        color = +("0x" + color.slice(1).replace(color.length < 5 && /./g, '$&$&'));
        r = color >> 16;
        g = color >> 8 & 255;
        b = color & 255;
    }

    // HSP (Highly Sensitive Poo) equation from http://alienryderflex.com/hsp.html
    hsp = Math.sqrt( 0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b) );

    // Using the HSP value, determine whether the color is light or dark
    if (hsp>127.5) { return 'light'; }
    else { return 'dark'; }
}