BlueSky Correct Terminology

Ensures BlueSky is using the correct terminology by replacing all instances of "post" with "skeet".

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name        BlueSky Correct Terminology
// @description Ensures BlueSky is using the correct terminology by replacing all instances of "post" with "skeet".
// @namespace   vivelin.net
// @match       https://bsky.app/*
// @grant       none
// @version     1.0.7
// @author      Vivelin
// @license     MIT
// ==/UserScript==
(function () {
    "use strict";

    /**
     * An object containing an array of RegExp patterns and string replacements for each language.
     */
    const allReplacements = {
        en: [
            { pattern: /\bPost(s)?\b/g, replacement: "Skeet$1" },
            { pattern: /\b([Rr]e)?post(s|ed)?\b/g, replacement: "$1skeet$2" },
        ],
    };

    /**
     * Controls whether user-generated content should be processed (true) or left alone (false, default).
     */
    const shouldReplaceSkeetContent = false;

    /**
     * Replaces the string, if necessary.
     * @param {string} text The original text to replace.
     * @param replacements An array of objects with the RegExp pattern and string replacement.
     * @returns {string} The replaced text.
     */
    function replaceText(text, replacements) {
        if (!text) return text;

        for (const replacement of replacements) {
            if (text.match(replacement.pattern)) {
                text = text.replaceAll(
                    replacement.pattern,
                    replacement.replacement,
                );
            }
        }

        return text;
    }

    /**
     * Recursively processes and replaces all instances of text.
     * @param {Node} node The node to process.
     * @returns {void}
     */
    function processNode(node, replacements) {
        node = node || document.body;

        if (node instanceof Text) {
            node.data = replaceText(node.data, replacements);
        } else if (node instanceof HTMLElement) {
            if (
                node.isContentEditable ||
                (!shouldReplaceSkeetContent &&
                    (node.dataset.wordWrap === "1" ||
                        node.dataset.testid === "profileHeaderDisplayName" ||
                        node.ariaLabel === "Expand alt text" ||
                        (node instanceof HTMLAnchorElement &&
                            node.href &&
                            node.href.match(/\/profile\//))))
            ) {
                return;
            }

            for (const child of node.childNodes) {
                processNode(child, replacements);
            }
        }
    }

    function observeCallback(_mutationList, _observer) {
        const lang = document.getElementsByTagName("html")[0].lang || "en";
        const replacements = allReplacements[lang];
        if (replacements) {
            processNode(document.body, replacements);
        }
    }

    const observer = new MutationObserver(observeCallback);
    observer.observe(document.body, { childList: true, subtree: true });
})();