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