nicolaslegland / fragilite

// ==UserScript==
// @name        fragilite
// @author      Nicolas Le Gland
// @copyright   2018-2022, nicolaslegland (https://openuserjs.org/users/nicolaslegland)
// @description Fragilité oculaire. Retire les points medians, pour celles-et-ceux qui ne tolèrent pas leur usage en écriture inclusive.
// @downloadURL https://openuserjs.org/src/scripts/nicolaslegland/fragilite.user.js
// @exclude     https://openuserjs.org/scripts/nicolaslegland/fragilite/source
// @grant       none
// @homepageURL https://openuserjs.org/scripts/nicolaslegland/fragilite
// @icon        https://www.w3.org/html/logo/downloads/HTML5_Badge_32.png
// @license     GPL-3.0-or-later
// @match       *://*/*
// @run-at      document-end
// @supportURL  https://openuserjs.org/scripts/nicolaslegland/fragilite/issues
// @updateURL   https://openuserjs.org/src/scripts/nicolaslegland/fragilite.user.js
// @version     12
// ==/UserScript==

// Find middot
function gFind()
{
    // Suspend observer
    g_oObserver.disconnect();
    window.clearTimeout(g_oTimer);

    // Create a document walker
    var
    l_oNode,
    l_oText,
    l_oWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {acceptNode: function (a_oNode)
    {
        // Skip whitespace and focused element
        if (('' === a_oNode.nodeValue.trim()) || (document.activeElement.isContentEditable && document.activeElement.contains(a_oNode)))
        {
            return NodeFilter.FILTER_SKIP;
        }
        return NodeFilter.FILTER_ACCEPT;
    }
    }, false);

    // Walk the document
    while (null !== (l_oNode = l_oWalker.nextNode()))
    {
        // Replace text
        l_oNode.nodeValue = gReplace(l_oNode.nodeValue);
    }

    // Restart observer
    g_oObserver.observe(document.body,
    {
        childList: true,
        subtree: true,
    });
}

// Replace middot
function gReplace(a_sText)
{
    // Loop through replacement map
    var l_iCount = g_oMap.length;
    for (var l_iIndex = 0; l_iCount !== l_iIndex; ++l_iIndex)
    {
        // Apply one replacement rule
        a_sText = a_sText.replace(g_oMap[l_iIndex][0], g_oMap[l_iIndex][1]);
    }
    return a_sText;
}

// Convert HTML to text
function gText(a_sHTML)
{
    // Helper element
    var l_oElement = document.createElement("textarea");
    l_oElement.innerHTML = a_sHTML;
    return l_oElement.value;
}

// Replacement map
var
g_oMap =
[
    ['([•·.-])(?:e|ine|le|ne|se|te)\\1', ''],
    ['(.)[•·.-]l?e([ ,s]|$)', '$1$2'],
    ['eur[•·.-](?:euse|rice)', 'eur'],
    ['[•·.-](?:te)?(s)(?:\/s)?', '$1'],
]
.map(function (a_oRule)
{
    // Convert to a regular expression
    a_oRule[0] = new RegExp(gText(a_oRule[0]), 'gi');
    a_oRule[1] = gText(a_oRule[1]);
    return a_oRule;
}),

// Obverse additions of nodes
g_oObserver = new MutationObserver(function ()
{
    // Rearm delayed process
    window.clearTimeout(g_oTimer);
    g_oTimer = window.setTimeout(gFind, 1000);
}),

// Delay process
g_oTimer = window.setTimeout(gFind, 1000);

// Self test
[
    // https://openuserjs.org/scripts/nicolaslegland/fragiliteoculaire
    ['celles-et-ceux', 'celles-et-ceux'],

    // https://www.freenews.fr/freenews-edition-nationale-299/iliad-6/emploi-free-recrute-des-technicien%C2%B7ne%C2%B7s-itinerant%C2%B7e%C2%B7s-resume
    ['Free recrute des technicien·ne·s itinérant·e·s', 'Free recrute des techniciens itinérants'],

    // https://www.gamekult.com/jeux/gran-turismo-sport-3050570077/test.html
    ['apprenti·e·s pilotes', 'apprentis pilotes'],
    ['joueur·se·s plus aguerri·e·s', 'joueurs plus aguerris'],
    ['être prêt·e pour claquer un joli chrono', 'être prêt pour claquer un joli chrono'],
    ['être amené·e à croiser', 'être amené à croiser'],
    ['24 concurrent·e·s', '24 concurrents'],
    ['un·e ou deux potes', 'un ou deux potes'],
    ['parties entre ami·e·s', 'parties entre amis'],
    ['comportement des concurrent·e·s', 'comportement des concurrents'],
    ['étourdi·e·s de la pédale de frein', 'étourdis de la pédale de frein'],
    ['calmer les ardeurs des malotru·e·s', 'calmer les ardeurs des malotrus'],
    ['vous êtes impliqué·e·s', 'vous êtes impliqués'],
    ['des taré·e·s du volant', 'des tarés du volant'],
    ['vous êtes impliqué·e dans des accidents', 'vous êtes impliqué dans des accidents'],
    ['donner aux joueur·se·s', 'donner aux joueurs'],
    ['bousiller les chronos des copain·ine·s', 'bousiller les chronos des copains'],
    ['animé·e·s par la même passion', 'animés par la même passion'],
    ['futur·e·s fans', 'futurs fans'],
    ['néophyte bienvenu·e', 'néophyte bienvenu'],

    // https://www.mairie02.paris.fr/ma-mairie/petite-enfance/assistant-e-s-maternel-le-s-agree-e-s-29
    ['assistant-e-s maternel-le-s agréé-e-s', 'assistants maternels agréés'],
    ['un-e assistant-e maternel-le agréé-e', 'un assistant maternel agréé'],
    ['pour être agréé-e, l\'assistant-e maternel-le doit', 'pour être agréé, l\'assistant maternel doit'],
    ['l\'assistant-e maternel-le doit être affilié-e', 'l\'assistant maternel doit être affilié'],
    ['emploi d\'un-e assistant-e maternel-le', 'emploi d\'un assistant maternel'],

    // https://twitter.com/RocwoSifwedi/status/1565336779145695233
    ['tou·tes', 'tous'],
    ['tou•tes', 'tous'],
    ['tou•tes/s', 'tous'],
    ['tou•s', 'tous'],

    // https://twitter.com/MathisHammel/status/1565332210886258690
    ['Comme certain·es s\'offusquent', 'Comme certains s\'offusquent'],
    ['Si vous êtes trop fermé·e d\'esprit', 'Si vous êtes trop fermé d\'esprit'],
    ['un mini "tou.te.s" ils sont pas prêts pour développeur.euse', 'un mini "tous" ils sont pas prêts pour développeur'],
]
.map(function (a_oTest)
{
    // Compare expectations and result
    var
    l_sSource = gText(a_oTest[0]),
    l_sResult = gReplace(l_sSource),
    l_sTarget = gText(a_oTest[1]);
    if (l_sResult !== l_sTarget)
    {
        // Log mismatch
        console.log('Source: ' + l_sSource);
        console.log('Result: ' + l_sResult);
        console.log('Target: ' + l_sTarget);
    }
});