wowchemy-theming.js 8.0 KB

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