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 @@
-