Sleazy Fork is available in English.

Romeo Additions

Allows to hide users, display their information on tiles, and enhances the Radar.

Від 28.12.2022. Дивіться остання версія.

// ==UserScript==
// @name           Romeo Additions
// @name:de        Romeo Additions
// @namespace      https://greasyfork.org/en/users/723211-ray/
// @version        3.1.0
// @description    Allows to hide users, display their information on tiles, and enhances the Radar.
// @description:de Ermöglicht das Verstecken von Benutzern, die Anzeige ihrer Details auf Kacheln, und verbessert den Radar.
// @author         -Ray-, Djamana
// @match          *://*.romeo.com/*
// @license        MIT
// ==/UserScript==

// ==== Dependencies ====

// ---- https://github.com/CoeJoder/GM_wrench ----

function addCss(css) {
    let style = document.createElement('style');
    style.type = 'text/css';
    if (style.styleSheet) {
        style.styleSheet.cssText = css;
    } else {
        style.appendChild(document.createTextNode(css));
    }
    document.head.appendChild(style);
};

function waitForKeyElements(selectorOrFunction, callback, waitOnce, interval, maxIntervals) {
    if (typeof waitOnce === 'undefined') {
        waitOnce = true;
    }
    if (typeof interval === 'undefined') {
        interval = 300;
    }
    if (typeof maxIntervals === 'undefined') {
        maxIntervals = -1;
    }
    var targetNodes = (typeof selectorOrFunction === 'function')
    ? selectorOrFunction()
    : document.querySelectorAll(selectorOrFunction);

    var targetsFound = targetNodes && targetNodes.length > 0;
    if (targetsFound) {
        targetNodes.forEach(function (targetNode) {
            var attrAlreadyFound = 'data-userscript-alreadyFound';
            var alreadyFound = targetNode.getAttribute(attrAlreadyFound) || false;
            if (!alreadyFound) {
                var cancelFound = callback(targetNode);
                if (cancelFound) {
                    targetsFound = false;
                }
                else {
                    targetNode.setAttribute(attrAlreadyFound, true);
                }
            }
        });
    }

    if (maxIntervals !== 0 && !(targetsFound && waitOnce)) {
        maxIntervals -= 1;
        setTimeout(function () {
            waitForKeyElements(selectorOrFunction, callback, waitOnce, interval, maxIntervals);
        }, interval);
    }
};

// ==== CSS ====

addCss(`
:root {
    --message-line-clamp: 2;
    --tile-headline-white-space: nowrap;
}

.tile p[class^="SpecialText-"] { white-space:var(--tile-headline-white-space); }
#messenger div[class^="TruncateBlock__Content-sc-"] { -webkit-line-clamp:var(--message-line-clamp); }

.js-romeo-badge { display:none; } /* hide PLUS icon as PLUS is faked to enhance tiles */
#visits > .layer__container--wider { width:unset; max-width:1227px; } /* wider visitor list */
#visits div[class^="UnlockMoreVisitorsGrid"] { display:none; } /* hide PLUS message at bottom of visitor grid */
div[data-testid="desktop-image"] { background-image:none; } /* hide models on login page */

.listitem__highlight--footprint { pointer-events:none; } /* allow clicking tile actions above stamps */
.tile__bar { background:#3030307F; backdrop-filter:blur(8px); position:absolute; left:0; top:0; visibility:hidden; z-index:1; }
.tile__bar_action { display:inline-block; color:white; padding:8px; }
.tile__bar_action:hover { background-color:#00A3E4; }
.tile__bar_action:active { background-color:#06648B; }
.tile:hover .tile__bar { visibility:visible; }

#version { color:white; }
`);

// ==== Script ====

function addElement(parent, html) {
    parent.insertAdjacentHTML("beforeend", html);
    return parent.lastChild;
}

function onElement(selector, callback) {
    waitForKeyElements(selector, callback, false, 500);
}

// ---- Language ----

const _strings = {
    display: {
        de: "Anzeige",
        en: "Display"
    },
    enhancedTiles: {
        de: "Erweiterte Kacheln",
        en: "Enhanced tiles"
    },
    enhancedTilesDesc: {
        de: "Zeigt alle Benutzerdetails auf den Kacheln. Im Radar wird dies Benutzer mit großen Kacheln darstellen.",
        en: "Shows all user details on tiles. The radar will display users with large tiles."
    },
    extensionTitle: {
        en: "Romeo Additions"
    },
    fullHeadlines: {
        de: "Vollständige Überschriften anzeigen",
        en: "Show full headlines"
    },
    fullHeadlinesDesc: {
        de: "Zeigt auch lange Profilüberschriften ungekürzt auf Kacheln.",
        en: "Shows even long profile headlines completely on tiles."
    },
    fullMessages: {
        de: "Vollständige Nachrichten anzeigen",
        en: "Show full messages"
    },
    fullMessagesDesc: {
        de: "Zeigt Nachrichten ungekürzt in der Nachrichtenliste.",
        en: "Shows messages without truncation in the message list."
    },
    hiddenUsers: {
        de: "Ausgeblendete Benutzer",
        en: "Hidden users"
    },
    hideActivities: {
        de: "Auch Activities verstecken",
        en: "Also hide activities"
    },
    hideActivitiesDesc: {
        de: "Versteckt ausgeblendete Benutzer auch im Activity Stream.",
        en: "Removes hidden users even in the activity stream."
    },
    hideLikes: {
        de: "Auch Likes auf Bilder verstecken",
        en: "Also hide likes on pictures"
    },
    hideLikesDesc: {
        de: "Versteckt ausgeblendete Benutzer auch in der Liste an Likes von Bildern.",
        en: "Removes hidden users even in the list of likes on pictures."
    },
    hideMessages: {
        de: "Auch Nachrichten verstecken",
        en: "Also hide messages"
    },
    hideMessagesDesc: {
        de: "Versteckt ausgeblendete Benutzer auch in der Nachrichtenliste.",
        en: "Removes hidden users even in the message list."
    },
    hideUser: {
        de: "Benutzer ausblenden",
        en: "Hide user"
    },
    maxAge: {
        de: "Maximales Alter",
        en: "Maximal age"
    },
    messages: {
        de: "Nachrichten",
        en: "Messages"
    },
    minAge: {
        de: "Minimales Alter",
        en: "Minimal age"
    },
    sendEnter: {
        de: "Enter sendet Nachricht",
        en: "Enter sends message"
    },
    sendEnterDesc: {
        de: "Wenn deaktiviert, erzeugt Enter einen Absatz und Strg+Enter versendet die Nachricht.",
        en: "If disabled, Enter creates a new line instead, and Ctrl+Enter sends the message."
    },
    tiles: {
        de: "Benutzerkacheln",
        en: "User tiles"
    },
    typingNotifications: {
        de: "Tippbenachrichtigungen",
        en: "Typing notifications"
    },
    typingNotificationsDesc: {
        de: "Wenn deaktiviert, können Empfänger nicht mehr sehen, dass eine Nachricht verfasst wird.",
        en: "If disabled, receivers can no longer see that a message is being composed."
    },
    viewFullImage: {
        de: "Bild vergrößern",
        en: "View full image"
    },
};

function translate(key) {
    const lang = document.documentElement.getAttribute("lang") || "en";
    const translations = _strings[key];
    return translations
        ? translations[lang] || translations.en || "%" + key + "%"
        : "%" + key + "%";
}

// ---- Settings ----

const settingsNs = "RA_SETTINGS:";

function load(name, fallback) {
    const value = localStorage.getItem(settingsNs + name);
    return value === "false" ? false
        : value ? value
        : fallback;
}
function save(name, value) {
    localStorage.setItem(settingsNs + name, value);
}

function getEnhancedTiles() {
    return load("enhancedTiles", true);
}
function getFullHeadlines() {
    return load("fullHeadlines", true);
}
function getFullMessages() {
    return load("fullMessages", true);
}
function getHiddenMaxAge() {
    return load("hiddenMaxAge", 99);
}
function getHiddenMinAge() {
    return load("hiddenMinAge", 18);
}
function getHiddenUsers() {
    return new Set(JSON.parse(load("hiddenUsers", "[]")));
}
function getHideActivities() {
    return load("hideActivities", true);
}
function getHideLikes() {
    return load("hideLikes", true);
}
function getHideMessages() {
    return load("hideMessages", false);
}
function getSendEnter() {
    return load("sendEnter", true);
}
function getTypingNotifications() {
    return load("typingNotifications", true);
}
function setEnhancedTiles(value) {
    save("enhancedTiles", value);
}
function setFullHeadlines(value) {
    document.documentElement.style.setProperty("--tile-headline-white-space", value ? "unset" : "nowrap");
    save("fullHeadlines", value);
}
function setFullMessages(value) {
    document.documentElement.style.setProperty("--message-line-clamp", value ? "unset" : "2");
    save("fullMessages", value);
}
function setHiddenMaxAge(value) {
    save("hiddenMaxAge", value);
}
function setHiddenMinAge(value) {
    save("hiddenMinAge", value);
}
function setHideActivities(value) {
    save("hideActivities", value);
}
function setHideLikes(value) {
    save("hideLikes", value);
}
function setHideMessages(value) {
    save("hideMessages", value);
}
function setSendEnter(value) {
    save("sendEnter", value);
}
function setTypingNotifications(value) {
    save("typingNotifications", value);
}
function setUserHidden(username, hide) {
    let hiddenUsers = getHiddenUsers();
    if (hide) {
        hiddenUsers.add(username);
    } else {
        hiddenUsers.delete(username);
    }
    save("hiddenUsers", JSON.stringify(Array.from(hiddenUsers)));
}

// ---- XHR ----

function filterUser(user, hiddenMaxAge, hiddenMinAge, hiddenNames) {
    return (!user.personal
            || user.personal.age >= hiddenMinAge
            && user.personal.age <= hiddenMaxAge)
        && !hiddenNames.has(user.name);
}

function getApiVerb(url) {
    if (url.includes("/api/stream")) {
        return "stream";
    }
    // Extract verb in "/api/v#/verb?" or "/api/+/verb?".
    const matches = url.match("/api/(v[0-9]|\\+)/(.*)\\?".replaceAll("/", "\\/"));
    if (matches && matches.length === 3) {
        return matches.at(-1);
    }
    return undefined;
}

function proxyXhr() {
    const realOpen = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
        this.addEventListener("load", () => {
            //console.log(`[RA] ${method} ${url}`);
            if (method !== "GET") {
                return;
            }
            const verb = getApiVerb(url);
            const isJson = verb !== "stream" && typeof this.response === "string";
            let reply = isJson ? JSON.parse(this.response) : this.response;
            //console.log(`[RA] ${verb}`, reply);

            // Modify interesting data.
            switch (verb) {
                case "messages/conversations":
                    reply = xhrHideMessages(reply);
                    break;
                case "notifications/activity-stream":
                    reply = xhrHideActivities(reply);
                    break;
                case "profiles":
                case "visitors":
                case "visits":
                    reply = xhrRestorePlusVisit(reply);
                    reply = xhrEnhanceUsers(reply);
                    break;
                case "reactions/pictures/basic":
                    reply = xhrHideLikes(reply);
                    break;
            }

            // Write back possibly modified data.
            Object.defineProperty(this, "responseText", { writable: true });
            this.responseText = isJson ? JSON.stringify(reply) : reply;
        });

        // Forward to client.
        return realOpen.apply(this, arguments);
    }
}

function xhrHideActivities(reply) {
    if (!getHideActivities()) {
        return reply;
    }
    // Remove hidden users.
    return reply.filter(x => filterUser(x.partner, getHiddenMaxAge(), getHiddenMinAge(), getHiddenUsers()));
}

function xhrHideLikes(reply) {
    if (!getHideLikes()) {
        return reply;
    }
    // Remove hidden users.
    reply.items = reply.items.filter(x => filterUser(x.user_id, getHiddenMaxAge(), getHiddenMinAge(), getHiddenUsers()));
    return reply;
}

function xhrHideMessages(reply) {
    if (!getHideMessages()) {
        return reply;
    }
    // Remove hidden users.
    reply.items = reply.items.filter(x => filterUser(x.chat_partner, getHiddenMaxAge(), getHiddenMinAge(), getHiddenUsers()));
    return reply;
}

function xhrEnhanceUsers(reply) {
    if (!reply.items) {
        return;
    }
    // Remove hidden users.
    const enhancedTiles = getEnhancedTiles();
    const hiddenMaxAge = getHiddenMaxAge();
    const hiddenMinAge = getHiddenMinAge();
    const hiddenNames = getHiddenUsers();
    let newItems = [];
    for (let item of reply.items) {
        if (filterUser(item, hiddenMaxAge, hiddenMinAge, hiddenNames)) {
            // Show as "large tiles" to display user details everywhere.
            if (enhancedTiles && item.display) {
                item.display.large_tile = true;
            }
            newItems.push(item);
        }
    }
    reply.items = newItems;
    return reply;
}

function xhrRestorePlusVisit(reply) {
    // Restore PLUS-visible visitors.
    reply.items_limited = reply.items_total;
    return reply;
}

// ---- Tile UI ----

onElement(`.tile:not(div[class*="tile--loading--"]) > .reactView`, el => {
    const tile = el.closest(".tile");
    if (!tile) {
        return;
    }
    // Extract user name from link.
    const a = tile.querySelector("a");
    const username = a.href.substring(a.href.indexOf("profile/")).split("/")[1];
    // Extract user avatar from link div.
    const div = a.firstChild;
    const divImg = window.getComputedStyle(div).getPropertyValue("background-image");
    const imgUrl = divImg.substring(divImg.indexOf('"') + 1, divImg.lastIndexOf('"'));
    // Add action bar.
    const tileBar = addElement(tile, `<div class="tile__bar"></div>`);
    addHideUserAction(tileBar, tile, username);
    addShowImageAction(tileBar, imgUrl);
});

function addShowImageAction(tileBar, url) {
    if (url.endsWith(".svg")) { // ignore "no photo" placeholders
        return;
    }
    const fileName = url.substring(url.lastIndexOf("/") + 1);
    const origUrl = "/img/usr/original/0x0/" + fileName;
    const action = addElement(tileBar, `<a class="tile__bar_action" href="${origUrl}" title="${translate("viewFullImage")}">
        <span class="icon icon-picture">
    </a>`);
    action.addEventListener("click", e => {
        e.preventDefault();
        const spotlightContainer = document.querySelector("#spotlight-container");
        const layer = addElement(spotlightContainer, `<div class="layer layer--spotlight" style="top:0;z-index:100;">
            <img src="${origUrl}"></img>
        </div>`);
        layer.addEventListener("click", e => layer.remove());
    });
}

function addHideUserAction(tileBar, tile, username) {
    const action = addElement(tileBar, `<a class="tile__bar_action" href="#" title="${translate("hideUser")}">
        <span class="icon icon-hide-visit">
    </a>`);
    action.addEventListener("click", e => {
        e.preventDefault();
        setUserHidden(username, true);
        tile.style.display = "none";
    });
}

// ---- Messaging UI ----

onElement(".js-send-region.layout-item > div", el => {
    el.addEventListener("keydown", e => {
        // Prevent site event handler from sending message or typing notifications.
        const enter = e.key === "Enter";
        const send = enter && (getSendEnter() || e.ctrlKey);
        const allow = send || getTypingNotifications() && !enter;
        if (!allow) {
            e.stopPropagation();
        }
    }, true);
});

// ---- Sidebar UI ----

function openPane(ul, link) {
    // Open pane.
    const layerContent = document.querySelector("#offcanvas-nav > .js-layer-content");
    layerContent.classList.add("is-open");
    // Replace pane contents.
    const pane = layerContent.querySelector(".js-side-content");
    pane.replaceChildren();
    addElement(pane, `
    <div class="layout layout--vertical layout--consume">
        <div class="layout-item layout-item--consume layout layout--vertical">

            <div class="layout-item settings__navigation p l-hidden-sm">
                <div class="js-title typo-section-navigation">${translate("extensionTitle")}</div>
            </div>

            <div class="layout-item layout-item--consume">
                <div class="js-content js-scrollable fit scrollable">
                    <div class="p">
                        <div class="settings__key">
                            <div>
                                <span>${translate("tiles")}</span>
                            </div>
                            <div class="separator separator--alt separator--narrow [ mb ] "></div>

                            <div class="layout layout--v-center">
                                <div class="layout-item [ 6/12--sm ]">
                                    <span>${translate("enhancedTiles")}</span>
                                </div>
                                <div class="layout-item [ 6/12--sm ]">
                                    <div class="js-toggle-show-headlines pull-right">
                                        <div>
                                            <span class="ui-toggle ui-toggle--default ui-toggle--right">
                                                <input class="ui-toggle__input" type="checkbox" id="ra_enhancedTiles">
                                                <label class="ui-toggle__label" for="ra_enhancedTiles" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
                                            </span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div>
                                <div class="settings__description">${translate("enhancedTilesDesc")}</div>
                            </div>

                            <div class="layout layout--v-center">
                                <div class="layout-item [ 6/12--sm ]">
                                    <span>${translate("fullHeadlines")}</span>
                                </div>
                                <div class="layout-item [ 6/12--sm ]">
                                    <div class="js-toggle-show-headlines pull-right">
                                        <div>
                                            <span class="ui-toggle ui-toggle--default ui-toggle--right">
                                                <input class="ui-toggle__input" type="checkbox" id="ra_fullHeadlines">
                                                <label class="ui-toggle__label" for="ra_fullHeadlines" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
                                            </span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div>
                                <div class="settings__description">${translate("fullHeadlinesDesc")}</div>
                            </div>
                        </div>

                        <div class="settings__key">
                            <div>
                                <span>${translate("messages")}</span>
                            </div>
                            <div class="separator separator--alt separator--narrow [ mb ] "></div>

                            <div class="layout layout--v-center">
                                <div class="layout-item [ 6/12--sm ]">
                                    <span>${translate("fullMessages")}</span>
                                </div>
                                <div class="layout-item [ 6/12--sm ]">
                                    <div class="js-toggle-show-headlines pull-right">
                                        <div>
                                            <span class="ui-toggle ui-toggle--default ui-toggle--right">
                                                <input class="ui-toggle__input" type="checkbox" id="ra_fullMessages">
                                                <label class="ui-toggle__label" for="ra_fullMessages" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
                                            </span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div>
                                <div class="settings__description">${translate("fullMessagesDesc")}</div>
                            </div>

                            <div class="layout layout--v-center">
                                <div class="layout-item [ 6/12--sm ]">
                                    <span>${translate("typingNotifications")}</span>
                                </div>
                                <div class="layout-item [ 6/12--sm ]">
                                    <div class="js-toggle-show-headlines pull-right">
                                        <div>
                                            <span class="ui-toggle ui-toggle--default ui-toggle--right">
                                                <input class="ui-toggle__input" type="checkbox" id="ra_typingNotifications">
                                                <label class="ui-toggle__label" for="ra_typingNotifications" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
                                            </span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div>
                                <div class="settings__description">${translate("typingNotificationsDesc")}</div>
                            </div>

                            <div class="layout layout--v-center">
                                <div class="layout-item [ 6/12--sm ]">
                                    <span>${translate("sendEnter")}</span>
                                </div>
                                <div class="layout-item [ 6/12--sm ]">
                                    <div class="js-toggle-show-headlines pull-right">
                                        <div>
                                            <span class="ui-toggle ui-toggle--default ui-toggle--right">
                                                <input class="ui-toggle__input" type="checkbox" id="ra_sendEnter">
                                                <label class="ui-toggle__label" for="ra_sendEnter" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
                                            </span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div>
                                <div class="settings__description">${translate("sendEnterDesc")}</div>
                            </div>
                        </div>

                        <div class="settings__key">
                            <div>
                                <span>${translate("hiddenUsers")}</span>
                            </div>
                            <div class="separator separator--alt separator--narrow [ mb ] "></div>

                            <div class="layout layout--v-center">
                                <div class="layout-item [ 6/12--sm ]">
                                    <span>${translate("hideMessages")}</span>
                                </div>
                                <div class="layout-item [ 6/12--sm ]">
                                    <div class="js-toggle-show-headlines pull-right">
                                        <div>
                                            <span class="ui-toggle ui-toggle--default ui-toggle--right">
                                                <input class="ui-toggle__input" type="checkbox" id="ra_hideMessages">
                                                <label class="ui-toggle__label" for="ra_hideMessages" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
                                            </span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div>
                                <div class="settings__description">${translate("hideMessagesDesc")}</div>
                            </div>

                            <div class="layout layout--v-center">
                                <div class="layout-item [ 6/12--sm ]">
                                    <span>${translate("hideActivities")}</span>
                                </div>
                                <div class="layout-item [ 6/12--sm ]">
                                    <div class="js-toggle-show-headlines pull-right">
                                        <div>
                                            <span class="ui-toggle ui-toggle--default ui-toggle--right">
                                                <input class="ui-toggle__input" type="checkbox" id="ra_hideActivities">
                                                <label class="ui-toggle__label" for="ra_hideActivities" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
                                            </span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div>
                                <div class="settings__description">${translate("hideActivitiesDesc")}</div>
                            </div>

                            <div class="layout layout--v-center">
                                <div class="layout-item [ 6/12--sm ]">
                                    <span>${translate("hideLikes")}</span>
                                </div>
                                <div class="layout-item [ 6/12--sm ]">
                                    <div class="js-toggle-show-headlines pull-right">
                                        <div>
                                            <span class="ui-toggle ui-toggle--default ui-toggle--right">
                                                <input class="ui-toggle__input" type="checkbox" id="ra_hideLikes">
                                                <label class="ui-toggle__label" for="ra_hideLikes" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
                                            </span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div>
                                <div class="settings__description">${translate("hideLikesDesc")}</div>
                            </div>

                            <div class="settings__key">
                                <div class="layout layout--v-center">
                                    <div class="layout-item [ 6/12--sm ]">
                                        <span>${translate("minAge")}</span>
                                    </div>
                                    <div class="layout-item [ 6/12--sm ]">
                                        <input class="input input--block" id="ra_hiddenMinAge" type="number" min="18" max="99"/>
                                    </div>
                                </div>
                            </div>

                            <div class="settings__key">
                                <div class="layout layout--v-center">
                                    <div class="layout-item [ 6/12--sm ]">
                                        <span>${translate("maxAge")}</span>
                                    </div>
                                    <div class="layout-item [ 6/12--sm ]">
                                        <input class="input input--block" id="ra_hiddenMaxAge" type="number" min="18" max="99"/>
                                    </div>
                                </div>
                            </div>

                            <div class="settings__key">
                                <div class="js-grid-stats-selector">
                                    <div>
                                        <ul class="js-list tags-list tags-list--centered" id="ra_hiddenUsers"/>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>`);
    // Handle tile settings.
    const inEnhancedTiles = pane.querySelector("#ra_enhancedTiles");
    inEnhancedTiles.checked = getEnhancedTiles();
    inEnhancedTiles.addEventListener("change", e => setEnhancedTiles(e.target.checked));
    const inFullHeadlines = pane.querySelector("#ra_fullHeadlines");
    inFullHeadlines.checked = getFullHeadlines();
    inFullHeadlines.addEventListener("change", e => setFullHeadlines(e.target.checked));
    // Handle message settings.
    const inFullMessages = pane.querySelector("#ra_fullMessages");
    inFullMessages.checked = getFullMessages();
    inFullMessages.addEventListener("change", e => setFullMessages(e.target.checked));
    const inTypingNotifications = pane.querySelector("#ra_typingNotifications");
    inTypingNotifications.checked = getTypingNotifications();
    inTypingNotifications.addEventListener("change", e => setTypingNotifications(e.target.checked));
    const inSendEnter = pane.querySelector("#ra_sendEnter");
    inSendEnter.checked = getSendEnter();
    inSendEnter.addEventListener("change", e => setSendEnter(e.target.checked));
    // Handle hidden interacctions.
    const inHideMessages = pane.querySelector("#ra_hideMessages");
    inHideMessages.checked = getHideMessages();
    inHideMessages.addEventListener("change", e => setHideMessages(e.target.checked));
    const inHideActivities = pane.querySelector("#ra_hideActivities");
    inHideActivities.checked = getHideActivities();
    inHideActivities.addEventListener("change", e => setHideActivities(e.target.checked));
    const inHideLikes = pane.querySelector("#ra_hideLikes");
    inHideLikes.checked = getHideLikes();
    inHideLikes.addEventListener("change", e => setHideLikes(e.target.checked));
    // Handle hidden age.
    const inMinAge = pane.querySelector("#ra_hiddenMinAge");
    const inMaxAge = pane.querySelector("#ra_hiddenMaxAge");
    let minAge = getHiddenMinAge();
    let maxAge = getHiddenMaxAge();
    inMinAge.value = minAge;
    inMaxAge.value = maxAge;
    inMinAge.addEventListener("change", e => {
        minAge = parseInt(e.target.value);
        setHiddenMinAge(minAge);
        if (minAge > maxAge) {
            maxAge = minAge;
            setHiddenMaxAge(maxAge);
            inMaxAge.val(maxAge);
        }
    });
    inMaxAge.addEventListener("change", e => {
        maxAge = parseInt(e.target.value);
        setHiddenMaxAge(maxAge);
        if (maxAge < minAge) {
            minAge = maxAge;
            setHiddenMinAge(minAge);
            inMinAge.val(minAge);
        }
    });
    // Handle hidden user list.
    const hiddenUsers = pane.querySelector("#ra_hiddenUsers");
    const users = Array.from(getHiddenUsers()).sort(Intl.Collator().compare);
    for (const user of users) {
        const li = addElement(hiddenUsers, `<li class="tags-list__item">
            <a class="js-tag ui-tag ui-tag--removable ui-tag--selected" href="#">
                <span class="ui-tag__label">${user}</span>
            </a>
        </li>`);
        li.querySelector("a").addEventListener("click", e => {
            setUserHidden(e.target.innerHTML, false);
            e.target.closest(".tags-list__item").style.display = "none";
        });
    };
}

onElement("li.js-settings > div.accordion > ul", el => {
    // Add extension menu item.
    const linkClass = el.querySelector("a").className;
    const link = addElement(el, `<li>
        <div>
            <a class="${linkClass}" href="/me/romeoadditions">${translate("extensionTitle")}</a>
        </div>
    </li>`);
    link.addEventListener("click", e => {
        if (link.classList.contains("is-selected")) {
            link.classList.remove("is-selected");
        } else {
            link.classList.add("is-selected");
            setTimeout(() => openPane(el, link)); // delayed execution to force open panel
        }
    });
    // Deselect menu item if others are clicked.
    for (const linkOther of el.querySelectorAll("li")) {
        if (linkOther !== link) {
            linkOther.addEventListener("click", e => link.classList.remove("is-selected"));
        }
    }
});

onElement(`div[class^="Version--"]`, el => {
    el.innerHTML += `<br>
        <a id="version" href="https://greasyfork.org/en/scripts/419514" target="blank">Romeo Additions 3.1.0</a>`;
});

// ---- On Load ----

(function () {
    "use strict";

    setFullHeadlines(getFullHeadlines());
    setFullMessages(getFullMessages());
    proxyXhr();
})();