wowchemy-theming.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. // Check if re-render required.
  142. if (!init) {
  143. // If request to render light when light variation already rendered, return.
  144. // If request to render dark when dark variation already rendered, return.
  145. if (
  146. (isDarkTheme === false && !body.classList.contains('dark')) ||
  147. (isDarkTheme === true && body.classList.contains('dark'))
  148. ) {
  149. return;
  150. }
  151. }
  152. if (isDarkTheme === false) {
  153. if (!init) {
  154. // Only fade in the page when changing the theme variation.
  155. Object.assign(document.body.style, {opacity: 0, visibility: 'visible'});
  156. fadeIn(document.body, 600);
  157. }
  158. body.classList.remove('dark');
  159. if (codeHlEnabled) {
  160. console.debug('Setting HLJS theme to light');
  161. if (codeHlLight) {
  162. codeHlLight.disabled = false;
  163. }
  164. if (codeHlDark) {
  165. codeHlDark.disabled = true;
  166. }
  167. }
  168. if (diagramEnabled) {
  169. console.debug('Initializing Mermaid with light theme');
  170. if (init) {
  171. /** @namespace window.mermaid **/
  172. window.mermaid.initialize({theme: 'default', securityLevel: 'loose'});
  173. } else {
  174. // Have to reload to re-initialise Mermaid with the new theme and re-parse the Mermaid code blocks.
  175. location.reload();
  176. }
  177. }
  178. } else if (isDarkTheme === true) {
  179. if (!init) {
  180. // Only fade in the page when changing the theme variation.
  181. Object.assign(document.body.style, {opacity: 0, visibility: 'visible'});
  182. fadeIn(document.body, 600);
  183. }
  184. body.classList.add('dark');
  185. if (codeHlEnabled) {
  186. console.debug('Setting HLJS theme to dark');
  187. if (codeHlLight) {
  188. codeHlLight.disabled = true;
  189. }
  190. if (codeHlDark) {
  191. codeHlDark.disabled = false;
  192. }
  193. }
  194. if (diagramEnabled) {
  195. console.debug('Initializing Mermaid with dark theme');
  196. if (init) {
  197. /** @namespace window.mermaid **/
  198. window.mermaid.initialize({theme: 'dark', securityLevel: 'loose'});
  199. } else {
  200. // Have to reload to re-initialise Mermaid with the new theme and re-parse the Mermaid code blocks.
  201. location.reload();
  202. }
  203. }
  204. }
  205. }
  206. /**
  207. * onMediaQueryListEvent.
  208. *
  209. * @param {MediaQueryListEvent} event
  210. * @returns {undefined}
  211. */
  212. function onMediaQueryListEvent(event) {
  213. if (!canChangeTheme()) {
  214. // Changing theme variation is not allowed by admin.
  215. return;
  216. }
  217. const darkModeOn = event.matches;
  218. console.debug(`OS dark mode preference changed to ${darkModeOn ? '🌒 on' : '☀️ off'}.`);
  219. let currentThemeVariation = getThemeMode();
  220. let isDarkTheme;
  221. if (currentThemeVariation === 2) {
  222. if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
  223. // The visitor prefers dark themes.
  224. isDarkTheme = true;
  225. } else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
  226. // The visitor prefers light themes.
  227. isDarkTheme = false;
  228. } else {
  229. // The visitor does not have a day or night preference, so use the theme's default setting.
  230. isDarkTheme = window.wc.isSiteThemeDark;
  231. }
  232. renderThemeVariation(isDarkTheme, currentThemeVariation);
  233. }
  234. }
  235. export {
  236. canChangeTheme,
  237. initThemeVariation,
  238. changeThemeModeClick,
  239. renderThemeVariation,
  240. getThemeMode,
  241. onMediaQueryListEvent,
  242. };