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