wowchemy-theming.js 8.6 KB

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