diff --git a/public/js/common.js b/public/js/common.js index 0406dbf..a80fc53 100644 --- a/public/js/common.js +++ b/public/js/common.js @@ -1,3 +1,7 @@ +/** + * --- Alpine helper functions + */ + /** * Set an alpine.js property * @@ -21,3 +25,123 @@ const setProp = (key, prop, selector = '#root') => { const getProp = (key, selector = '#root') => { return $(selector).get(0).__x.$data[key]; }; + +/** + * --- Theme related functions + * Note: In the comments below we treat "theme" and "theme setting" + * differently. A theme can have only two values, either "dark" or + * "light", while a theme setting can have the third value "system". + */ + +/** + * Check if the system setting prefers dark theme. + * from https://flaviocopes.com/javascript-detect-dark-mode/ + * + * @function preferDarkMode + * @return {bool} + */ +const preferDarkMode = () => { + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; +}; + +/** + * Check whether a given string represents a valid theme setting + * + * @function validThemeSetting + * @param {string} theme - The string representing the theme setting + * @return {bool} + */ +const validThemeSetting = (theme) => { + return ['dark', 'light', 'system'].indexOf(theme) >= 0; +}; + +/** + * Load theme setting from local storage, or use 'light' + * + * @function loadThemeSetting + * @return {string} A theme setting ('dark', 'light', or 'system') + */ +const loadThemeSetting = () => { + let str = localStorage.getItem('theme'); + if (!str || !validThemeSetting(str)) str = 'light'; + return str; +}; + +/** + * Load the current theme (not theme setting) + * + * @function loadTheme + * @return {string} The current theme to use ('dark' or 'light') + */ +const loadTheme = () => { + let setting = loadThemeSetting(); + if (setting === 'system') { + setting = preferDarkMode() ? 'dark' : 'light'; + } + return setting; +}; + +/** + * Save a theme setting + * + * @function saveThemeSetting + * @param {string} setting - A theme setting + */ +const saveThemeSetting = setting => { + if (!validThemeSetting(setting)) setting = 'light'; + localStorage.setItem('theme', setting); +}; + +/** + * Toggle the current theme. When the current theme setting is 'system', it + * will be changed to either 'light' or 'dark' + * + * @function toggleTheme + */ +const toggleTheme = () => { + const theme = loadTheme(); + const newTheme = theme === 'dark' ? 'light' : 'dark'; + saveThemeSetting(newTheme); + setTheme(newTheme); +}; + +/** + * Apply a theme, or load a theme and then apply it + * + * @function setTheme + * @param {string?} theme - (Optional) The theme to apply. When omitted, use + * `loadTheme` to get a theme and apply it. + */ +const setTheme = (theme) => { + if (!theme) theme = loadTheme(); + if (theme === 'dark') { + $('html').css('background', 'rgb(20, 20, 20)'); + $('body').addClass('uk-light'); + $('.uk-card').addClass('uk-card-secondary'); + $('.uk-card').removeClass('uk-card-default'); + $('.ui-widget-content').addClass('dark'); + } else { + $('html').css('background', ''); + $('body').removeClass('uk-light'); + $('.uk-card').removeClass('uk-card-secondary'); + $('.uk-card').addClass('uk-card-default'); + $('.ui-widget-content').removeClass('dark'); + } +}; + +// do it before document is ready to prevent the initial flash of white on +// most pages +setTheme(); +$(() => { + // hack for the reader page + setTheme(); + + // on system dark mode setting change + if (window.matchMedia) { + window.matchMedia('(prefers-color-scheme: dark)') + .addEventListener('change', event => { + if (loadThemeSetting() === 'system') + setTheme(event.matches ? 'dark' : 'light'); + }); + } +}); diff --git a/public/js/theme.js b/public/js/theme.js deleted file mode 100644 index 3cd6690..0000000 --- a/public/js/theme.js +++ /dev/null @@ -1,72 +0,0 @@ -// https://flaviocopes.com/javascript-detect-dark-mode/ -const preferDarkMode = () => { - return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; -}; - -const validThemeSetting = (theme) => { - return ['dark', 'light', 'system'].indexOf(theme) >= 0; -}; - -// dark / light / system -const loadThemeSetting = () => { - let str = localStorage.getItem('theme'); - if (!str || !validThemeSetting(str)) str = 'light'; - return str; -}; - -// dark / light -const loadTheme = () => { - let setting = loadThemeSetting(); - if (setting === 'system') { - setting = preferDarkMode() ? 'dark' : 'light'; - } - return setting; -}; - -const saveThemeSetting = setting => { - if (!validThemeSetting(setting)) setting = 'light'; - localStorage.setItem('theme', setting); -}; - -// when toggled, Auto will be changed to light or dark -const toggleTheme = () => { - const theme = loadTheme(); - const newTheme = theme === 'dark' ? 'light' : 'dark'; - saveThemeSetting(newTheme); - setTheme(newTheme); -}; - -const setTheme = (theme) => { - if (!theme) theme = loadTheme(); - if (theme === 'dark') { - $('html').css('background', 'rgb(20, 20, 20)'); - $('body').addClass('uk-light'); - $('.uk-card').addClass('uk-card-secondary'); - $('.uk-card').removeClass('uk-card-default'); - $('.ui-widget-content').addClass('dark'); - } else { - $('html').css('background', ''); - $('body').removeClass('uk-light'); - $('.uk-card').removeClass('uk-card-secondary'); - $('.uk-card').addClass('uk-card-default'); - $('.ui-widget-content').removeClass('dark'); - } -}; - -// do it before document is ready to prevent the initial flash of white on -// most pages -setTheme(); - -$(() => { - // hack for the reader page - setTheme(); - - // on system dark mode setting change - if (window.matchMedia) { - window.matchMedia('(prefers-color-scheme: dark)') - .addEventListener('change', event => { - if (loadThemeSetting() === 'system') - setTheme(event.matches ? 'dark' : 'light'); - }); - } -}); diff --git a/src/views/components/head.html.ecr b/src/views/components/head.html.ecr index e4fdfcf..c65ddea 100644 --- a/src/views/components/head.html.ecr +++ b/src/views/components/head.html.ecr @@ -15,5 +15,4 @@ -