wowchemy-theming.js 7.9 KB

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