wowchemy-theming.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. /*************************************************
  2. * Wowchemy
  3. * https://github.com/wowchemy/wowchemy-hugo-modules
  4. *
  5. * Wowchemy Theming System
  6. * Supported Modes: {0: Light, 1: Dark, 2: Auto}
  7. **************************************************/
  8. import {fadeIn} from './wowchemy-animation';
  9. const body = document.body;
  10. function getThemeMode() {
  11. return parseInt(localStorage.getItem('wcTheme') || 2);
  12. }
  13. function canChangeTheme() {
  14. // If var is set, then user is allowed to change the theme variation.
  15. return Boolean(window.wc.darkLightEnabled);
  16. }
  17. // initThemeVariation is first called directly after <body> to prevent
  18. // flashing between the default theme mode and the user's choice.
  19. function initThemeVariation() {
  20. if (!canChangeTheme()) {
  21. console.debug('User theming disabled.');
  22. return {
  23. isDarkTheme: window.wc.isSiteThemeDark,
  24. themeMode: window.wc.isSiteThemeDark ? 1 : 0,
  25. };
  26. }
  27. console.debug('User theming enabled.');
  28. let isDarkTheme;
  29. let currentThemeMode = getThemeMode();
  30. console.debug(`User's theme variation: ${currentThemeMode}`);
  31. switch (currentThemeMode) {
  32. case 0:
  33. isDarkTheme = false;
  34. break;
  35. case 1:
  36. isDarkTheme = true;
  37. break;
  38. default:
  39. if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
  40. // The visitor prefers dark themes and switching to the dark variation is allowed by admin.
  41. isDarkTheme = true;
  42. } else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
  43. // The visitor prefers light themes and switching to the light variation is allowed by admin.
  44. isDarkTheme = false;
  45. } else {
  46. // Use the site's default theme variation based on `light` in the theme file.
  47. isDarkTheme = window.wc.isSiteThemeDark;
  48. }
  49. break;
  50. }
  51. if (isDarkTheme && !body.classList.contains('dark')) {
  52. console.debug('Applying Wowchemy dark theme');
  53. document.body.classList.add('dark');
  54. } else if (!isDarkTheme && body.classList.contains('dark')) {
  55. console.debug('Applying Wowchemy light theme');
  56. document.body.classList.remove('dark');
  57. }
  58. return {
  59. isDarkTheme: isDarkTheme,
  60. themeMode: currentThemeMode,
  61. };
  62. }
  63. function changeThemeModeClick(newMode) {
  64. if (!canChangeTheme()) {
  65. console.debug('Cannot change theme - user theming disabled.');
  66. return;
  67. }
  68. let isDarkTheme;
  69. switch (newMode) {
  70. case 0:
  71. localStorage.setItem('wcTheme', '0');
  72. isDarkTheme = false;
  73. console.debug('User changed theme variation to Light.');
  74. break;
  75. case 1:
  76. localStorage.setItem('wcTheme', '1');
  77. isDarkTheme = true;
  78. console.debug('User changed theme variation to Dark.');
  79. break;
  80. default:
  81. localStorage.setItem('wcTheme', '2');
  82. if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
  83. // The visitor prefers dark themes and switching to the dark variation is allowed by admin.
  84. isDarkTheme = true;
  85. } else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
  86. // The visitor prefers light themes and switching to the light variation is allowed by admin.
  87. isDarkTheme = false;
  88. } else {
  89. // Use the site's default theme variation based on `light` in the theme file.
  90. isDarkTheme = window.wc.isSiteThemeDark;
  91. }
  92. console.debug('User changed theme variation to Auto.');
  93. break;
  94. }
  95. renderThemeVariation(isDarkTheme, newMode);
  96. }
  97. function showActiveTheme(mode) {
  98. let linkLight = document.querySelector('.js-set-theme-light');
  99. let linkDark = document.querySelector('.js-set-theme-dark');
  100. let linkAuto = document.querySelector('.js-set-theme-auto');
  101. if (linkLight === null) {
  102. return;
  103. }
  104. switch (mode) {
  105. case 0:
  106. // Light.
  107. linkLight.classList.add('dropdown-item-active');
  108. linkDark.classList.remove('dropdown-item-active');
  109. linkAuto.classList.remove('dropdown-item-active');
  110. break;
  111. case 1:
  112. // Dark.
  113. linkLight.classList.remove('dropdown-item-active');
  114. linkDark.classList.add('dropdown-item-active');
  115. linkAuto.classList.remove('dropdown-item-active');
  116. break;
  117. default:
  118. // Auto.
  119. linkLight.classList.remove('dropdown-item-active');
  120. linkDark.classList.remove('dropdown-item-active');
  121. linkAuto.classList.add('dropdown-item-active');
  122. break;
  123. }
  124. }
  125. /**
  126. * Render theme variation (day or night).
  127. *
  128. * @param {boolean} isDarkTheme
  129. * @param {int} themeMode - {0: Light, 1: Dark, 2: Auto}
  130. * @param {boolean} init - true only when called on document ready
  131. * @returns {undefined}
  132. */
  133. function renderThemeVariation(isDarkTheme, themeMode = 2, init = false) {
  134. // Is code highlighting enabled in site config?
  135. const codeHlLight = document.querySelector('link[title=hl-light]');
  136. const codeHlDark = document.querySelector('link[title=hl-dark]');
  137. const codeHlEnabled = codeHlLight !== null || codeHlDark !== null;
  138. const diagramEnabled = document.querySelector('script[title=mermaid]') !== null;
  139. // Update active theme mode in navbar theme selector.
  140. showActiveTheme(themeMode);
  141. // Dispatch `wcThemeChange` event to support themeable user plugins.
  142. const themeChangeEvent = new CustomEvent('wcThemeChange', {detail: {isDarkTheme: () => isDarkTheme}});
  143. document.dispatchEvent(themeChangeEvent);
  144. // Check if re-render required.
  145. if (!init) {
  146. // If request to render light when light variation already rendered, return.
  147. // If request to render dark when dark variation already rendered, return.
  148. if (
  149. (isDarkTheme === false && !body.classList.contains('dark')) ||
  150. (isDarkTheme === true && body.classList.contains('dark'))
  151. ) {
  152. return;
  153. }
  154. }
  155. if (isDarkTheme === false) {
  156. if (!init) {
  157. // Only fade in the page when changing the theme variation.
  158. Object.assign(document.body.style, {opacity: 0, visibility: 'visible'});
  159. fadeIn(document.body, 600);
  160. }
  161. body.classList.remove('dark');
  162. if (codeHlEnabled) {
  163. console.debug('Setting HLJS theme to light');
  164. if (codeHlLight) {
  165. codeHlLight.disabled = false;
  166. }
  167. if (codeHlDark) {
  168. codeHlDark.disabled = true;
  169. }
  170. }
  171. if (diagramEnabled) {
  172. console.debug('Initializing Mermaid with light theme');
  173. if (init) {
  174. /** @namespace window.mermaid **/
  175. window.mermaid.initialize({startOnLoad: true, theme: 'default', securityLevel: 'loose'});
  176. } else {
  177. // Have to reload to re-initialise Mermaid with the new theme and re-parse the Mermaid code blocks.
  178. location.reload();
  179. }
  180. }
  181. } else if (isDarkTheme === true) {
  182. if (!init) {
  183. // Only fade in the page when changing the theme variation.
  184. Object.assign(document.body.style, {opacity: 0, visibility: 'visible'});
  185. fadeIn(document.body, 600);
  186. }
  187. body.classList.add('dark');
  188. if (codeHlEnabled) {
  189. console.debug('Setting HLJS theme to dark');
  190. if (codeHlLight) {
  191. codeHlLight.disabled = true;
  192. }
  193. if (codeHlDark) {
  194. codeHlDark.disabled = false;
  195. }
  196. }
  197. if (diagramEnabled) {
  198. console.debug('Initializing Mermaid with dark theme');
  199. if (init) {
  200. /** @namespace window.mermaid **/
  201. window.mermaid.initialize({startOnLoad: true, theme: 'dark', securityLevel: 'loose'});
  202. } else {
  203. // Have to reload to re-initialise Mermaid with the new theme and re-parse the Mermaid code blocks.
  204. location.reload();
  205. }
  206. }
  207. }
  208. }
  209. /**
  210. * onMediaQueryListEvent.
  211. *
  212. * @param {MediaQueryListEvent} event
  213. * @returns {undefined}
  214. */
  215. function onMediaQueryListEvent(event) {
  216. if (!canChangeTheme()) {
  217. // Changing theme variation is not allowed by admin.
  218. return;
  219. }
  220. const darkModeOn = event.matches;
  221. console.debug(`OS dark mode preference changed to ${darkModeOn ? '🌒 on' : '☀️ off'}.`);
  222. let currentThemeVariation = getThemeMode();
  223. let isDarkTheme;
  224. if (currentThemeVariation === 2) {
  225. if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
  226. // The visitor prefers dark themes.
  227. isDarkTheme = true;
  228. } else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
  229. // The visitor prefers light themes.
  230. isDarkTheme = false;
  231. } else {
  232. // The visitor does not have a day or night preference, so use the theme's default setting.
  233. isDarkTheme = window.wc.isSiteThemeDark;
  234. }
  235. renderThemeVariation(isDarkTheme, currentThemeVariation);
  236. }
  237. }
  238. export {
  239. canChangeTheme,
  240. initThemeVariation,
  241. changeThemeModeClick,
  242. renderThemeVariation,
  243. getThemeMode,
  244. onMediaQueryListEvent,
  245. };