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