1/** 2 * GP Dark Mode â toggle behavior (loaded in the footer). 3 * 4 * The initial theme is already applied by the inline head script, which also 5 * exposes its config on window.ogmGpdm. This script wires up the toggle button 6 * and, when configured, keeps unsaved visitors in sync with their OS theme. 7 */ 8(function () { 9 var html = document.documentElement; 10 // Bind every toggle on the page (menu bar + any [gp_dark_mode_toggle] 11 // shortcodes), not just the first one by id. 12 var toggles = document.querySelectorAll('.dm-toggle'); 13 14 var cfg = window.ogmGpdm || {}; 15 var respectSystem = !!cfg.respectSystem; 16 var defaultTheme = cfg.defaultTheme === 'dark' ? 'dark' : 'light'; 17 18 // ---------- Storage helpers (localStorage + cookie fallback) ---------- 19 20 function storageAvailable() { 21 try { 22 var k = '__theme_test__'; 23 localStorage.setItem(k, '1'); 24 localStorage.removeItem(k); 25 return true; 26 } catch (e) { 27 return false; 28 } 29 } 30 31 var hasLocalStorage = storageAvailable(); 32 33 function getCookie(name) { 34 try { 35 var m = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') + '=([^;]*)')); 36 return m ? decodeURIComponent(m[1]) : null; 37 } catch (e) { 38 return null; 39 } 40 } 41 42 function setCookie(name, value, days) { 43 try { 44 var expires = ''; 45 if (typeof days === 'number') { 46 var d = new Date(); 47 d.setTime(d.getTime() + days * 864e5); 48 expires = '; expires=' + d.toUTCString(); 49 } 50 document.cookie = 51 name + '=' + encodeURIComponent(value) + 52 expires + 53 '; path=/' + 54 '; samesite=lax'; 55 } catch (e) {} 56 } 57 58 function deleteCookie(name) { 59 setCookie(name, '', -1); 60 } 61 62 // The visitor's saved override ('dark' | 'light' | null). 63 function getPref() { 64 try { 65 if (hasLocalStorage) return localStorage.getItem('theme'); 66 } catch (e) {} 67 return getCookie('theme'); 68 } 69 70 function setPref(valueOrNull) { 71 try { 72 if (hasLocalStorage) { 73 if (valueOrNull === null) localStorage.removeItem('theme'); 74 else localStorage.setItem('theme', valueOrNull); 75 return; 76 } 77 } catch (e) {} 78 79 if (valueOrNull === null) deleteCookie('theme'); 80 else setCookie('theme', valueOrNull, 365); 81 } 82 83 function hasSavedPref() { 84 var p = getPref(); 85 return p === 'dark' || p === 'light'; 86 } 87 88 // ---------- Theme logic ---------- 89 90 function isDarkNow() { 91 return html.getAttribute('data-theme') === 'dark'; 92 } 93 94 function refreshDisqusIfPresent() { 95 if (!document.getElementById('disqus_thread')) return; 96 if (window.DISQUS && typeof window.DISQUS.reset === 'function') { 97 window.DISQUS.reset({ reload: true }); 98 } 99 } 100 101 function syncToggles(isDark) { 102 for (var i = 0; i < toggles.length; i++) { 103 toggles[i].setAttribute('aria-checked', isDark ? 'true' : 'false'); 104 } 105 } 106 107 function apply(theme, persist) { 108 var isDark = theme === 'dark'; 109 110 html.setAttribute('data-theme', isDark ? 'dark' : 'light'); 111 syncToggles(isDark); 112 113 if (persist) setPref(isDark ? 'dark' : 'light'); 114 115 refreshDisqusIfPresent(); 116 } 117 118 function toggleTheme() { 119 apply(isDarkNow() ? 'light' : 'dark', true); 120 } 121 122 // Wire each toggle once (the data flag guards against double-binding). 123 for (var t = 0; t < toggles.length; t++) { 124 (function (btn) { 125 if (btn.getAttribute('data-gpdm-bound') === '1') { 126 return; 127 } 128 btn.setAttribute('data-gpdm-bound', '1'); 129 btn.setAttribute('aria-checked', isDarkNow() ? 'true' : 'false'); 130 131 btn.addEventListener('click', toggleTheme); 132 133 btn.addEventListener('keydown', function (e) { 134 var key = e.key || e.code; 135 if (key === ' ' || key === 'Spacebar' || key === 'Enter') { 136 e.preventDefault(); 137 toggleTheme(); 138 } 139 }); 140 })(toggles[t]); 141 } 142 143 // Follow live OS theme changes â but only for visitors who haven't chosen, 144 // and only when the site is configured to respect the system preference. 145 if (respectSystem && window.matchMedia) { 146 var mq = window.matchMedia('(prefers-color-scheme: dark)'); 147 var onChange = function () { 148 if (hasSavedPref()) return; 149 // Mirror the head script's three-way resolution: a runtime drop to 150 // "no OS preference" falls back to the configured default, not light. 151 var theme; 152 if (window.matchMedia('(prefers-color-scheme: dark)').matches) { 153 theme = 'dark'; 154 } else if (window.matchMedia('(prefers-color-scheme: light)').matches) { 155 theme = 'light'; 156 } else { 157 theme = defaultTheme; 158 } 159 apply(theme, false); 160 }; 161 if (typeof mq.addEventListener === 'function') { 162 mq.addEventListener('change', onChange); 163 } else if (typeof mq.addListener === 'function') { 164 mq.addListener(onChange); // Older Safari. 165 } 166 } 167})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.