src/librustdoc/html/static/js/settings.js JAVASCRIPT 362 lines View on github.com → Search inside
1// Local js definitions:2/* global getSettingValue, updateLocalStorage, updateTheme */3/* global addClass, removeClass, onEach, onEachLazy */4/* global MAIN_ID, getVar, nonnull */56"use strict";78(function() {9    const isSettingsPage = window.location.pathname.endsWith("/settings.html");1011    /**12     * @overload {"theme"|"preferred-dark-theme"|"preferred-light-theme"}13     * @param {string} settingName14     * @param {string} value15     * @returns16     * @param {string} settingName17     * @param {string|boolean} value18     */19    function changeSetting(settingName, value) {20        if (settingName === "theme") {21            const useSystem = value === "system preference" ? "true" : "false";22            updateLocalStorage("use-system-theme", useSystem);23        }24        updateLocalStorage(settingName, "" + value);2526        switch (settingName) {27            case "theme":28            case "preferred-dark-theme":29            case "preferred-light-theme":30                updateTheme();31                updateLightAndDark();32                break;33            case "line-numbers":34                if (value === true) {35                    const f = window.rustdoc_add_line_numbers_to_examples;36                    if (f !== undefined) {37                        f();38                    }39                } else {40                    const f = window.rustdoc_remove_line_numbers_from_examples;41                    if (f !== undefined) {42                        f();43                    }44                }45                break;46            case "sans-serif-fonts":47            case "hide-sidebar":48            case "hide-toc":49            case "hide-modnav":50            case "word-wrap-source-code":51            case "hide-deprecated-items":52                if (value === true) {53                    addClass(document.documentElement, settingName);54                } else {55                    removeClass(document.documentElement, settingName);56                }57        }58    }5960    function showLightAndDark() {61        removeClass(document.getElementById("preferred-light-theme"), "hidden");62        removeClass(document.getElementById("preferred-dark-theme"), "hidden");63    }6465    function hideLightAndDark() {66        addClass(document.getElementById("preferred-light-theme"), "hidden");67        addClass(document.getElementById("preferred-dark-theme"), "hidden");68    }6970    function updateLightAndDark() {71        const useSystem = getSettingValue("use-system-theme");72        if (useSystem === "true" || (useSystem === null && getSettingValue("theme") === null)) {73            showLightAndDark();74        } else {75            hideLightAndDark();76        }77    }7879    /**80     * @param {HTMLElement} settingsElement81     */82    function setEvents(settingsElement) {83        updateLightAndDark();84        onEachLazy(settingsElement.querySelectorAll("input[type=\"checkbox\"]"), toggle => {85            const settingId = toggle.id;86            const settingValue = getSettingValue(settingId);87            if (settingValue !== null) {88                toggle.checked = settingValue === "true";89            }90            toggle.onchange = () => {91                changeSetting(toggle.id, toggle.checked);92            };93        });94        onEachLazy(95            settingsElement.querySelectorAll("input[type=\"radio\"]"),96            /** @param {HTMLInputElement} elem */97            elem => {98                const settingId = elem.name;99                let settingValue = getSettingValue(settingId);100                if (settingId === "theme") {101                    const useSystem = getSettingValue("use-system-theme");102                    if (useSystem === "true" || settingValue === null) {103                        // "light" is the default theme104                        settingValue = useSystem === "false" ? "light" : "system preference";105                    }106                }107                if (settingValue !== null && settingValue !== "null") {108                    elem.checked = settingValue === elem.value;109                }110                elem.addEventListener("change", () => {111                    changeSetting(elem.name, elem.value);112                });113            },114        );115    }116117    /**118     * This function builds the sections inside the "settings page". It takes a `settings` list119     * as argument which describes each setting and how to render it. It returns a string120     * representing the raw HTML.121     *122     * @param {Array<rustdoc.Setting>} settings123     *124     * @return {string}125     */126    function buildSettingsPageSections(settings) {127        let output = "";128129        for (const setting of settings) {130            const js_data_name = setting["js_name"];131            const setting_name = setting["name"];132133            if (setting["options"] !== undefined) {134                // This is a select setting.135                output += `\136<div class="setting-line" id="${js_data_name}">137    <div class="setting-radio-name">${setting_name}</div>138    <div class="setting-radio-choices">`;139                onEach(setting["options"], option => {140                    const checked = option === setting["default"] ? " checked" : "";141                    const full = `${js_data_name}-${option.replace(/ /g,"-")}`;142143                    output += `\144        <label for="${full}" class="setting-radio">145            <input type="radio" name="${js_data_name}"146                id="${full}" value="${option}"${checked}>147            <span>${option}</span>148        </label>`;149                });150                output += `\151    </div>152</div>`;153            } else {154                // This is a checkbox toggle.155                const checked = setting["default"] === true ? " checked" : "";156                output += `\157<div class="setting-line">\158    <label class="setting-check">\159        <input type="checkbox" id="${js_data_name}"${checked}>\160        <span>${setting_name}</span>\161    </label>\162</div>`;163            }164        }165        return output;166    }167168    /**169     * This function builds the "settings page" and returns the generated HTML element.170     *171     * @return {HTMLElement}172     */173    function buildSettingsPage() {174        const theme_list = getVar("themes");175        const theme_names = (theme_list === null ? "" : theme_list)176              .split(",").filter(t => t);177        theme_names.push("light", "dark", "ayu");178179        const settings = [180            {181                "name": "Theme",182                "js_name": "theme",183                "default": "system preference",184                "options": theme_names.concat("system preference"),185            },186            {187                "name": "Preferred light theme",188                "js_name": "preferred-light-theme",189                "default": "light",190                "options": theme_names,191            },192            {193                "name": "Preferred dark theme",194                "js_name": "preferred-dark-theme",195                "default": "dark",196                "options": theme_names,197            },198            {199                "name": "Auto-hide item contents for large items",200                "js_name": "auto-hide-large-items",201                "default": true,202            },203            {204                "name": "Auto-hide item methods' documentation",205                "js_name": "auto-hide-method-docs",206                "default": false,207            },208            {209                "name": "Auto-hide trait implementation documentation",210                "js_name": "auto-hide-trait-implementations",211                "default": false,212            },213            {214                "name": "Directly go to item in search if there is only one result",215                "js_name": "go-to-only-result",216                "default": false,217            },218            {219                "name": "Show line numbers on code examples",220                "js_name": "line-numbers",221                "default": false,222            },223            {224                "name": "Hide persistent navigation bar",225                "js_name": "hide-sidebar",226                "default": false,227            },228            {229                "name": "Hide table of contents",230                "js_name": "hide-toc",231                "default": false,232            },233            {234                "name": "Hide module navigation",235                "js_name": "hide-modnav",236                "default": false,237            },238            {239                "name": "Disable keyboard shortcuts",240                "js_name": "disable-shortcuts",241                "default": false,242            },243            {244                "name": "Use sans serif fonts",245                "js_name": "sans-serif-fonts",246                "default": false,247            },248            {249                "name": "Word wrap source code",250                "js_name": "word-wrap-source-code",251                "default": false,252            },253            {254                "name": "Hide deprecated items",255                "js_name": "hide-deprecated-items",256                "default": false,257            },258        ];259260        // Then we build the DOM.261        const elementKind = isSettingsPage ? "section" : "div";262        const innerHTML = `<div class="settings">${buildSettingsPageSections(settings)}</div>`;263        const el = document.createElement(elementKind);264        el.id = "settings";265        if (!isSettingsPage) {266            el.className = "popover";267        }268        el.innerHTML = innerHTML;269270        if (isSettingsPage) {271            const mainElem = document.getElementById(MAIN_ID);272            if (mainElem !== null) {273                mainElem.appendChild(el);274            }275        } else {276            el.setAttribute("tabindex", "-1");277            onEachLazy(document.querySelectorAll(".settings-menu"), menu => {278                if (menu.offsetWidth !== 0) {279                    menu.appendChild(el);280                    return true;281                }282            });283        }284        return el;285    }286287    const settingsMenu = buildSettingsPage();288289    function displaySettings() {290        settingsMenu.style.display = "";291        onEachLazy(document.querySelectorAll(".settings-menu"), menu => {292            if (menu.offsetWidth !== 0) {293                if (!menu.contains(settingsMenu) && settingsMenu.parentElement) {294                    settingsMenu.parentElement.removeChild(settingsMenu);295                    menu.appendChild(settingsMenu);296                }297                return true;298            }299        });300        onEachLazy(settingsMenu.querySelectorAll("input[type='checkbox']"), el => {301            const val = getSettingValue(el.id);302            const checked = val === "true";303            if (checked !== el.checked && val !== null) {304                el.checked = checked;305            }306        });307    }308309    /**310     * @param {FocusEvent} event311     */312    function settingsBlurHandler(event) {313        const isInPopover = onEachLazy(314            document.querySelectorAll(".settings-menu, .help-menu"),315            menu => {316                return menu.contains(document.activeElement) || menu.contains(event.relatedTarget);317            },318        );319        if (!isInPopover) {320            window.hidePopoverMenus();321        }322    }323324    if (!isSettingsPage) {325        // We replace the existing "onclick" callback.326        const settingsMenu = nonnull(document.getElementById("settings"));327        onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => {328            /** @param {MouseEvent} event */329            settingsButton.querySelector("a").onclick = event => {330                if (!(event.target instanceof Element) || settingsMenu.contains(event.target)) {331                    return;332                }333                event.preventDefault();334                const shouldDisplaySettings = settingsMenu.style.display === "none";335336                window.hideAllModals(false);337                if (shouldDisplaySettings) {338                    displaySettings();339                }340            };341            settingsButton.onblur = settingsBlurHandler;342            settingsButton.querySelector("a").onblur = settingsBlurHandler;343        });344        onEachLazy(settingsMenu.querySelectorAll("input"), el => {345            el.onblur = settingsBlurHandler;346        });347        settingsMenu.onblur = settingsBlurHandler;348    }349350    // We now wait a bit for the web browser to end re-computing the DOM...351    setTimeout(() => {352        setEvents(settingsMenu);353        // The setting menu is already displayed if we're on the settings page.354        if (!isSettingsPage) {355            displaySettings();356        }357        onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => {358            removeClass(settingsButton, "rotate");359        });360    }, 0);361})();

Code quality findings 27

Remove event listeners when no longer needed to prevent memory leaks
warning performance event-listener-leak
elem.addEventListener("change", () => {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (settingName === "theme") {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const useSystem = value === "system preference" ? "true" : "false";
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (settingName) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (value === true) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (f !== undefined) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (f !== undefined) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (value === true) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (useSystem === "true" || (useSystem === null && getSettingValue("theme") === null)) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (settingValue !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
toggle.checked = settingValue === "true";
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (settingId === "theme") {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (useSystem === "true" || settingValue === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
settingValue = useSystem === "false" ? "light" : "system preference";
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (settingValue !== null && settingValue !== "null") {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
elem.checked = settingValue === elem.value;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (setting["options"] !== undefined) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const checked = option === setting["default"] ? " checked" : "";
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const checked = setting["default"] === true ? " checked" : "";
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const theme_names = (theme_list === null ? "" : theme_list)
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (mainElem !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (menu.offsetWidth !== 0) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (menu.offsetWidth !== 0) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const checked = val === "true";
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (checked !== el.checked && val !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const shouldDisplaySettings = settingsMenu.style.display === "none";
Can lead to race conditions; consider Promises or async/await for better control
info correctness set-timeout
setTimeout(() => {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.