1/** 2 3The code below is based on the Doxygen Awesome project with some minor modifications 4https://github.com/jothepro/doxygen-awesome-css 5 6MIT License 7 8Copyright (c) 2021 - 2022 jothepro 9 10Permission is hereby granted, free of charge, to any person obtaining a copy 11of this software and associated documentation files (the "Software"), to deal 12in the Software without restriction, including without limitation the rights 13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14copies of the Software, and to permit persons to whom the Software is 15furnished to do so, subject to the following conditions: 16 17The above copyright notice and this permission notice shall be included in all 18copies or substantial portions of the Software. 19 20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26SOFTWARE. 27 28*/ 29 30class DarkModeToggle extends HTMLElement { 31 static icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="1.2em" width="1.2em"><g fill="none" fill-rule="evenodd"><path d="M0 0h24v24H0z"></path><rect width="1" height="3" x="12" fill="currentColor" rx=".5"></rect><rect width="1" height="3" x="12" y="21" fill="currentColor" rx=".5"></rect><rect width="1" height="3" x="22" y="10.5" fill="currentColor" rx=".5" transform="rotate(90 22.5 12)"></rect><rect width="1" height="3" x="1" y="10.5" fill="currentColor" rx=".5" transform="rotate(90 1.5 12)"></rect><rect width="1" height="3" x="19" y="3" fill="currentColor" rx=".5" transform="rotate(-135 19.5 4.5)"></rect><rect width="1" height="3" x="19" y="18" fill="currentColor" rx=".5" transform="rotate(135 19.5 19.5)"></rect><rect width="1" height="3" x="4" y="3" fill="currentColor" rx=".5" transform="scale(1 -1) rotate(45 15.37 0)"></rect><rect width="1" height="3" x="4" y="18" fill="currentColor" rx=".5" transform="scale(1 -1) rotate(-45 -42.57 0)"></rect><circle cx="12" cy="12" r="6.5" stroke="currentColor"></circle></g></svg>' 32 static icond = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="1.2em" width="1.2em"><g fill="none" fill-rule="evenodd"><path d="M0 0h24v24H0z"></path><rect width="1" height="3" x="12" fill="currentColor" rx=".5"></rect><rect width="1" height="3" x="12" y="21" fill="currentColor" rx=".5"></rect><rect width="1" height="3" x="22" y="10.5" fill="currentColor" rx=".5" transform="rotate(90 22.5 12)"></rect><rect width="1" height="3" x="1" y="10.5" fill="currentColor" rx=".5" transform="rotate(90 1.5 12)"></rect><rect width="1" height="3" x="19" y="3" fill="currentColor" rx=".5" transform="rotate(-135 19.5 4.5)"></rect><rect width="1" height="3" x="19" y="18" fill="currentColor" rx=".5" transform="rotate(135 19.5 19.5)"></rect><rect width="1" height="3" x="4" y="3" fill="currentColor" rx=".5" transform="scale(1 -1) rotate(45 15.37 0)"></rect><rect width="1" height="3" x="4" y="18" fill="currentColor" rx=".5" transform="scale(1 -1) rotate(-45 -42.57 0)"></rect><circle cx="12" cy="12" r="6.5" stroke="currentColor" fill="currentColor"></circle></g></svg>' 33 static title = "Toggle Light/Dark Mode" 34 35 static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode" 36 static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode" 37 38 static _staticConstructor = function() { 39 DarkModeToggle.enableDarkMode(DarkModeToggle.userPreference) 40 // Update the color scheme when the browsers preference changes 41 // without user interaction on the website. 42 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { 43 DarkModeToggle.onSystemPreferenceChanged() 44 }) 45 // Update the color scheme when the tab is made visible again. 46 // It is possible that the appearance was changed in another tab 47 // while this tab was in the background. 48 document.addEventListener("visibilitychange", visibilityState => { 49 if (document.visibilityState === 'visible') { 50 DarkModeToggle.onSystemPreferenceChanged() 51 } 52 }); 53 }() 54 55 static addButton() { 56 57 var tbuttons = document.getElementsByTagName("dark-mode-toggle"); 58 var toggleButton; 59 var titleArea = document.getElementById("titlearea"); 60 var searchBox = document.getElementById("MSearchBox"); 61 var mainMenu = document.getElementById("main-menu"); 62 var navRow1 = document.getElementById("navrow1"); 63 var mainMenuVisible = false; 64 if (!tbuttons.length){ 65 toggleButton = document.createElement('dark-mode-toggle') 66 toggleButton.title = DarkModeToggle.title 67 } else {toggleButton=tbuttons[0]} 68 69 70 if (DarkModeToggle.darkModeEnabled){ 71 toggleButton.innerHTML=DarkModeToggle.icond 72 } else { 73 toggleButton.innerHTML=DarkModeToggle.icon 74 } 75 76 if (mainMenu) { 77 var menuStyle = window.getComputedStyle(mainMenu); 78 mainMenuVisible = menuStyle.display!=='none' 79 } 80 var searchBoxPos1 = document.getElementById("searchBoxPos1"); 81 if (searchBox) { // (1) search box visible 82 searchBox.parentNode.appendChild(toggleButton) 83 } else if (navRow1) { // (2) no search box, static menu bar 84 var li = document.createElement('li'); 85 li.style = 'float: right;' 86 li.appendChild(toggleButton); 87 toggleButton.style = 'width: 24px; height: 25px; padding-top: 11px; float: right;'; 88 var row = document.querySelector('#navrow1 > ul:first-of-type'); 89 row.appendChild(li) 90 } else if (mainMenu && mainMenuVisible) { // (3) no search box + dynamic menu bar expanded 91 var li = document.createElement('li'); 92 li.style = 'float: right;' 93 li.appendChild(toggleButton); 94 toggleButton.style = 'width: 14px; height: 36px; padding-top: 10px; float: right;'; 95 mainMenu.appendChild(li) 96 } else if (searchBoxPos1) { // (4) no search box + dynamic menu bar collapsed 97 toggleButton.style = 'width: 24px; height: 36px; padding-top: 10px; float: right;'; 98 searchBoxPos1.style = 'top: 0px;' 99 searchBoxPos1.appendChild(toggleButton); 100 } else if (titleArea) { // (5) no search box and no navigation tabs 101 toggleButton.style = 'width: 24px; height: 24px; position: absolute; right: 0px; top: 34px;'; 102 titleArea.append(toggleButton); 103 } 104 } 105 106 static init() { 107 $(function() { 108 $(document).ready(function() { 109 110 $(document).ready(function(){ 111 DarkModeToggle.addButton(); 112 }) 113 $(window).resize(function(){ 114 DarkModeToggle.addButton(); 115 }) 116 DarkModeToggle.setDarkModeVisibility(DarkModeToggle.darkModeEnabled) 117 }) 118 }) 119 } 120 121 constructor() { 122 super(); 123 this.onclick=this.toggleDarkMode 124 } 125 126 127 static createCookie(name, value, days) { 128 if (days) { 129 var date = new Date(); 130 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 131 var expires = "; expires=" + date.toGMTString(); 132 } 133 else var expires = ""; 134 135 document.cookie = name + "=" + value + expires + "; path=/"; 136 } 137 138 static readCookie(name) { 139 var nameEQ = name + "="; 140 var ca = document.cookie.split(';'); 141 for (var i = 0; i < ca.length; i++) { 142 var c = ca[i]; 143 while (c.charAt(0) == ' ') c = c.substring(1, c.length); 144 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); 145 } 146 return null; 147 } 148 149 static eraseCookie(name) { 150 DarkModeToggle.createCookie(name, "", -1); 151 } 152 153 /** 154 * @returns `true` for dark-mode, `false` for light-mode system preference 155 */ 156 static get systemPreference() { 157 return window.matchMedia('(prefers-color-scheme: dark)').matches 158 } 159 160 static get prefersDarkModeInLightMode() { 161 if (window.chrome) { // Chrome supports localStorage in combination with file:// but not cookies 162 return localStorage.getItem(DarkModeToggle.prefersDarkModeInLightModeKey) 163 } else { // Other browsers support cookies in combination with file:// but not localStorage 164 return DarkModeToggle.readCookie('doxygen_prefers_dark')=='1' 165 } 166 } 167 168 static set prefersDarkModeInLightMode(preference) { 169 if (window.chrome) { 170 if (preference) { 171 localStorage.setItem(DarkModeToggle.prefersDarkModeInLightModeKey, true) 172 } else { 173 localStorage.removeItem(DarkModeToggle.prefersDarkModeInLightModeKey) 174 } 175 } else { 176 if (preference) { 177 DarkModeToggle.createCookie('doxygen_prefers_dark','1',365) 178 } else { 179 DarkModeToggle.eraseCookie('doxygen_prefers_dark') 180 } 181 } 182 } 183 184 static get prefersLightModeInDarkMode() { 185 if (window.chrome) { // Chrome supports localStorage in combination with file:// but not cookies 186 return localStorage.getItem(DarkModeToggle.prefersLightModeInDarkModeKey) 187 } else { // Other browsers support cookies in combination with file:// but not localStorage 188 return DarkModeToggle.readCookie('doxygen_prefers_light')=='1' 189 } 190 } 191 192 static set prefersLightModeInDarkMode(preference) { 193 if (window.chrome) { 194 if (preference) { 195 localStorage.setItem(DarkModeToggle.prefersLightModeInDarkModeKey, true) 196 } else { 197 localStorage.removeItem(DarkModeToggle.prefersLightModeInDarkModeKey) 198 } 199 } else { 200 if (preference) { 201 DarkModeToggle.createCookie('doxygen_prefers_light','1',365) 202 } else { 203 DarkModeToggle.eraseCookie('doxygen_prefers_light') 204 } 205 } 206 } 207 208 209 /** 210 * @returns `true` for dark-mode, `false` for light-mode user preference 211 */ 212 static get userPreference() { 213 return (!DarkModeToggle.systemPreference && DarkModeToggle.prefersDarkModeInLightMode) || 214 (DarkModeToggle.systemPreference && !DarkModeToggle.prefersLightModeInDarkMode) 215 } 216 217 static set userPreference(userPreference) { 218 DarkModeToggle.darkModeEnabled = userPreference 219 if (!userPreference) { 220 if (DarkModeToggle.systemPreference) { 221 DarkModeToggle.prefersLightModeInDarkMode = true 222 } else { 223 DarkModeToggle.prefersDarkModeInLightMode = false 224 } 225 } else { 226 if (!DarkModeToggle.systemPreference) { 227 DarkModeToggle.prefersDarkModeInLightMode = true 228 } else { 229 DarkModeToggle.prefersLightModeInDarkMode = false 230 } 231 } 232 DarkModeToggle.onUserPreferenceChanged() 233 } 234 235 static setDarkModeVisibility(enable) { 236 var darkModeStyle, lightModeStyle; 237 if(enable) { 238 darkModeStyle = 'inline-block'; 239 lightModeStyle = 'none' 240 } else { 241 darkModeStyle = 'none'; 242 lightModeStyle = 'inline-block' 243 } 244 document.querySelectorAll('.dark-mode-visible').forEach(function(el) { 245 el.style.display = darkModeStyle; 246 }); 247 document.querySelectorAll('.light-mode-visible').forEach(function(el) { 248 el.style.display = lightModeStyle; 249 }); 250 } 251 static enableDarkMode(enable) { 252 if(enable) { 253 DarkModeToggle.darkModeEnabled = true 254 document.documentElement.classList.add("dark-mode") 255 document.documentElement.classList.remove("light-mode") 256 } else { 257 DarkModeToggle.darkModeEnabled = false 258 document.documentElement.classList.remove("dark-mode") 259 document.documentElement.classList.add("light-mode") 260 } 261 DarkModeToggle.setDarkModeVisibility(enable) 262 } 263 264 static onSystemPreferenceChanged() { 265 DarkModeToggle.darkModeEnabled = DarkModeToggle.userPreference 266 DarkModeToggle.enableDarkMode(DarkModeToggle.darkModeEnabled) 267 } 268 269 static onUserPreferenceChanged() { 270 DarkModeToggle.enableDarkMode(DarkModeToggle.darkModeEnabled) 271 } 272 273 toggleDarkMode() { 274 DarkModeToggle.userPreference = !DarkModeToggle.userPreference 275 DarkModeToggle.addButton(); 276 } 277} 278 279customElements.define("dark-mode-toggle", DarkModeToggle); 280 281DarkModeToggle.init(); 282