• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Local js definitions:
2/* global getSettingValue, updateLocalStorage, updateTheme */
3/* global addClass, removeClass, onEach, onEachLazy, blurHandler, elemIsInParent */
4/* global MAIN_ID, getVar, getSettingsButton */
5
6"use strict";
7
8(function() {
9    const isSettingsPage = window.location.pathname.endsWith("/settings.html");
10
11    function changeSetting(settingName, value) {
12        if (settingName === "theme") {
13            const useSystem = value === "system preference" ? "true" : "false";
14            updateLocalStorage("use-system-theme", useSystem);
15        }
16        updateLocalStorage(settingName, value);
17
18        switch (settingName) {
19            case "theme":
20            case "preferred-dark-theme":
21            case "preferred-light-theme":
22                updateTheme();
23                updateLightAndDark();
24                break;
25            case "line-numbers":
26                if (value === true) {
27                    window.rustdoc_add_line_numbers_to_examples();
28                } else {
29                    window.rustdoc_remove_line_numbers_from_examples();
30                }
31                break;
32        }
33    }
34
35    function showLightAndDark() {
36        removeClass(document.getElementById("preferred-light-theme"), "hidden");
37        removeClass(document.getElementById("preferred-dark-theme"), "hidden");
38    }
39
40    function hideLightAndDark() {
41        addClass(document.getElementById("preferred-light-theme"), "hidden");
42        addClass(document.getElementById("preferred-dark-theme"), "hidden");
43    }
44
45    function updateLightAndDark() {
46        const useSystem = getSettingValue("use-system-theme");
47        if (useSystem === "true" || (useSystem === null && getSettingValue("theme") === null)) {
48            showLightAndDark();
49        } else {
50            hideLightAndDark();
51        }
52    }
53
54    function setEvents(settingsElement) {
55        updateLightAndDark();
56        onEachLazy(settingsElement.querySelectorAll("input[type=\"checkbox\"]"), toggle => {
57            const settingId = toggle.id;
58            const settingValue = getSettingValue(settingId);
59            if (settingValue !== null) {
60                toggle.checked = settingValue === "true";
61            }
62            toggle.onchange = function() {
63                changeSetting(this.id, this.checked);
64            };
65        });
66        onEachLazy(settingsElement.querySelectorAll("input[type=\"radio\"]"), elem => {
67            const settingId = elem.name;
68            let settingValue = getSettingValue(settingId);
69            if (settingId === "theme") {
70                const useSystem = getSettingValue("use-system-theme");
71                if (useSystem === "true" || settingValue === null) {
72                    // "light" is the default theme
73                    settingValue = useSystem === "false" ? "light" : "system preference";
74                }
75            }
76            if (settingValue !== null && settingValue !== "null") {
77                elem.checked = settingValue === elem.value;
78            }
79            elem.addEventListener("change", ev => {
80                changeSetting(ev.target.name, ev.target.value);
81            });
82        });
83    }
84
85    /**
86     * This function builds the sections inside the "settings page". It takes a `settings` list
87     * as argument which describes each setting and how to render it. It returns a string
88     * representing the raw HTML.
89     *
90     * @param {Array<Object>} settings
91     *
92     * @return {string}
93     */
94    function buildSettingsPageSections(settings) {
95        let output = "";
96
97        for (const setting of settings) {
98            const js_data_name = setting["js_name"];
99            const setting_name = setting["name"];
100
101            if (setting["options"] !== undefined) {
102                // This is a select setting.
103                output += `\
104<div class="setting-line" id="${js_data_name}">
105    <div class="setting-radio-name">${setting_name}</div>
106    <div class="setting-radio-choices">`;
107                onEach(setting["options"], option => {
108                    const checked = option === setting["default"] ? " checked" : "";
109                    const full = `${js_data_name}-${option.replace(/ /g,"-")}`;
110
111                    output += `\
112        <label for="${full}" class="setting-radio">
113            <input type="radio" name="${js_data_name}"
114                id="${full}" value="${option}"${checked}>
115            <span>${option}</span>
116        </label>`;
117                });
118                output += `\
119    </div>
120</div>`;
121            } else {
122                // This is a checkbox toggle.
123                const checked = setting["default"] === true ? " checked" : "";
124                output += `\
125<div class="setting-line">\
126    <label class="setting-check">\
127        <input type="checkbox" id="${js_data_name}"${checked}>\
128        <span>${setting_name}</span>\
129    </label>\
130</div>`;
131            }
132        }
133        return output;
134    }
135
136    /**
137     * This function builds the "settings page" and returns the generated HTML element.
138     *
139     * @return {HTMLElement}
140     */
141    function buildSettingsPage() {
142        const theme_names = getVar("themes").split(",").filter(t => t);
143        theme_names.push("light", "dark", "ayu");
144
145        const settings = [
146            {
147                "name": "Theme",
148                "js_name": "theme",
149                "default": "system preference",
150                "options": theme_names.concat("system preference"),
151            },
152            {
153                "name": "Preferred light theme",
154                "js_name": "preferred-light-theme",
155                "default": "light",
156                "options": theme_names,
157            },
158            {
159                "name": "Preferred dark theme",
160                "js_name": "preferred-dark-theme",
161                "default": "dark",
162                "options": theme_names,
163            },
164            {
165                "name": "Auto-hide item contents for large items",
166                "js_name": "auto-hide-large-items",
167                "default": true,
168            },
169            {
170                "name": "Auto-hide item methods' documentation",
171                "js_name": "auto-hide-method-docs",
172                "default": false,
173            },
174            {
175                "name": "Auto-hide trait implementation documentation",
176                "js_name": "auto-hide-trait-implementations",
177                "default": false,
178            },
179            {
180                "name": "Directly go to item in search if there is only one result",
181                "js_name": "go-to-only-result",
182                "default": false,
183            },
184            {
185                "name": "Show line numbers on code examples",
186                "js_name": "line-numbers",
187                "default": false,
188            },
189            {
190                "name": "Disable keyboard shortcuts",
191                "js_name": "disable-shortcuts",
192                "default": false,
193            },
194        ];
195
196        // Then we build the DOM.
197        const elementKind = isSettingsPage ? "section" : "div";
198        const innerHTML = `<div class="settings">${buildSettingsPageSections(settings)}</div>`;
199        const el = document.createElement(elementKind);
200        el.id = "settings";
201        if (!isSettingsPage) {
202            el.className = "popover";
203        }
204        el.innerHTML = innerHTML;
205
206        if (isSettingsPage) {
207            document.getElementById(MAIN_ID).appendChild(el);
208        } else {
209            el.setAttribute("tabindex", "-1");
210            getSettingsButton().appendChild(el);
211        }
212        return el;
213    }
214
215    const settingsMenu = buildSettingsPage();
216
217    function displaySettings() {
218        settingsMenu.style.display = "";
219    }
220
221    function settingsBlurHandler(event) {
222        blurHandler(event, getSettingsButton(), window.hidePopoverMenus);
223    }
224
225    if (isSettingsPage) {
226        // We replace the existing "onclick" callback to do nothing if clicked.
227        getSettingsButton().onclick = function(event) {
228            event.preventDefault();
229        };
230    } else {
231        // We replace the existing "onclick" callback.
232        const settingsButton = getSettingsButton();
233        const settingsMenu = document.getElementById("settings");
234        settingsButton.onclick = function(event) {
235            if (elemIsInParent(event.target, settingsMenu)) {
236                return;
237            }
238            event.preventDefault();
239            const shouldDisplaySettings = settingsMenu.style.display === "none";
240
241            window.hideAllModals();
242            if (shouldDisplaySettings) {
243                displaySettings();
244            }
245        };
246        settingsButton.onblur = settingsBlurHandler;
247        settingsButton.querySelector("a").onblur = settingsBlurHandler;
248        onEachLazy(settingsMenu.querySelectorAll("input"), el => {
249            el.onblur = settingsBlurHandler;
250        });
251        settingsMenu.onblur = settingsBlurHandler;
252    }
253
254    // We now wait a bit for the web browser to end re-computing the DOM...
255    setTimeout(() => {
256        setEvents(settingsMenu);
257        // The setting menu is already displayed if we're on the settings page.
258        if (!isSettingsPage) {
259            displaySettings();
260        }
261        removeClass(getSettingsButton(), "rotate");
262    }, 0);
263})();
264