diff --git a/.gitignore b/.gitignore index 5e068e1..4933660 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ /server/ui/dist /Makefile /mailpit* +/.idea *.old *.db diff --git a/server/ui-src/App.vue b/server/ui-src/App.vue index 8b3fd67..68a270a 100644 --- a/server/ui-src/App.vue +++ b/server/ui-src/App.vue @@ -933,7 +933,7 @@ export default { -
+
About @@ -1196,4 +1196,53 @@ export default {
+ + + + + + + + + + + + + + + + + + + + + diff --git a/server/ui-src/app.js b/server/ui-src/app.js index 4b0d6c8..dddf71b 100644 --- a/server/ui-src/app.js +++ b/server/ui-src/app.js @@ -3,5 +3,6 @@ import App from './App.vue'; import "./assets/styles.scss"; import "../../node_modules/bootstrap-icons/font/bootstrap-icons.scss"; import "bootstrap"; +import "./color-modes"; createApp(App).mount('#app'); diff --git a/server/ui-src/color-modes.js b/server/ui-src/color-modes.js new file mode 100644 index 0000000..f9c437e --- /dev/null +++ b/server/ui-src/color-modes.js @@ -0,0 +1,94 @@ +/*! + * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors + * Licensed under the Creative Commons Attribution 3.0 Unported License. + */ + +(() => { + 'use strict'; + + const getStoredTheme = () => localStorage.getItem('theme'); + const setStoredTheme = (theme) => localStorage.setItem('theme', theme); + + const getPreferredTheme = () => { + const storedTheme = getStoredTheme(); + if (storedTheme) { + return storedTheme; + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light'; + }; + + const setTheme = (theme) => { + if ( + theme === 'auto' && + window.matchMedia('(prefers-color-scheme: dark)').matches + ) { + document.documentElement.setAttribute('data-bs-theme', 'dark'); + } else { + document.documentElement.setAttribute('data-bs-theme', theme); + } + }; + + setTheme(getPreferredTheme()); + + const showActiveTheme = (theme, focus = false) => { + const themeSwitcher = document.querySelector('#bd-theme'); + + if (!themeSwitcher) { + return; + } + + const themeSwitcherText = document.querySelector('#bd-theme-text'); + const activeThemeIcon = document.querySelector('.theme-icon-active use'); + const btnToActive = document.querySelector( + `[data-bs-theme-value="${theme}"]` + ); + const svgOfActiveBtn = btnToActive + .querySelector('svg use') + .getAttribute('href'); + + document.querySelectorAll('[data-bs-theme-value]').forEach((element) => { + element.classList.remove('active'); + element.setAttribute('aria-pressed', 'false'); + }); + + btnToActive.classList.add('active'); + btnToActive.setAttribute('aria-pressed', 'true'); + activeThemeIcon.setAttribute('href', svgOfActiveBtn); + const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`; + themeSwitcher.setAttribute('aria-label', themeSwitcherLabel); + + if (focus) { + themeSwitcher.focus(); + } + }; + + window + .matchMedia('(prefers-color-scheme: dark)') + .addEventListener('change', () => { + const storedTheme = getStoredTheme(); + if (storedTheme !== 'light' && storedTheme !== 'dark') { + setTheme(getPreferredTheme()); + } + }); + + window.addEventListener('DOMContentLoaded', () => { + showActiveTheme(getPreferredTheme()); + + document.querySelectorAll('[data-bs-theme-value]').forEach((toggle) => { + toggle.addEventListener('click', () => { + const theme = toggle.getAttribute('data-bs-theme-value'); + setStoredTheme(theme); + setTheme(theme); + showActiveTheme(theme, true); + }); + }); + }); +})(); + +document.querySelectorAll('[data-bs-toggle="popover"]').forEach((popover) => { + new bootstrap.Popover(popover); +});