Update implementation
This commit is contained in:
@@ -48,6 +48,17 @@
|
||||
transition: transform .3s;
|
||||
}
|
||||
|
||||
.mobile-navigation-actions > * {
|
||||
padding-block: .9rem;
|
||||
padding-inline: var(--content-bounds-padding);
|
||||
cursor: pointer;
|
||||
transition: background-color .3s;
|
||||
}
|
||||
|
||||
.mobile-navigation-actions > *:hover, .mobile-navigation-actions > *:active {
|
||||
background-color: var(--color-widget-background-highlight);
|
||||
}
|
||||
|
||||
.mobile-navigation:has(.mobile-navigation-page-links-input:checked) .hamburger-icon {
|
||||
--spacing: 7px;
|
||||
color: var(--color-primary);
|
||||
@@ -60,6 +71,7 @@
|
||||
|
||||
.mobile-navigation-page-links {
|
||||
border-top: 1px solid var(--color-widget-content-border);
|
||||
border-bottom: 1px solid var(--color-widget-content-border);
|
||||
padding: 20px var(--content-bounds-padding);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,25 +1,4 @@
|
||||
.dark {
|
||||
--scheme: ;
|
||||
--bgh: 240;
|
||||
--bgs: 8%;
|
||||
--bgl: 9%;
|
||||
--bghs: var(--bgh), var(--bgs);
|
||||
--cm: 1;
|
||||
--tsm: 1;
|
||||
}
|
||||
|
||||
.light {
|
||||
--scheme: 100% -;
|
||||
--bgh: 240;
|
||||
--bgs: 50%;
|
||||
--bgl: 98%;
|
||||
--bghs: var(--bgh), var(--bgs);
|
||||
--cm: 1;
|
||||
--tsm: 1;
|
||||
--color-primary: hsl(43, 50%, 70%);
|
||||
}
|
||||
|
||||
.light-scheme {
|
||||
:root[data-scheme=light] {
|
||||
--scheme: 100% -;
|
||||
}
|
||||
|
||||
@@ -240,7 +219,7 @@ kbd:active {
|
||||
max-width: 1100px;
|
||||
}
|
||||
|
||||
.page-center-vertically .page {
|
||||
.page.center-vertically {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
@@ -277,6 +256,8 @@ kbd:active {
|
||||
}
|
||||
|
||||
.nav {
|
||||
overflow-x: auto;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
gap: var(--header-items-gap);
|
||||
}
|
||||
@@ -315,82 +296,72 @@ kbd:active {
|
||||
color: var(--color-text-highlight);
|
||||
}
|
||||
|
||||
/*
|
||||
### Theme Dropdown ###
|
||||
*/
|
||||
.theme-dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
right: 0;
|
||||
.theme-choices {
|
||||
--presets-per-row: 2;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--presets-per-row), 1fr);
|
||||
align-items: center;
|
||||
gap: 1.35rem;
|
||||
}
|
||||
|
||||
.dropdown-button {
|
||||
padding: 10px 15px;
|
||||
background: var(--color-widget-background);
|
||||
border: 1px solid var(--color-widget-content-border);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
.theme-choices:has(> :nth-child(3)) {
|
||||
--presets-per-row: 3;
|
||||
}
|
||||
|
||||
.theme-preset {
|
||||
background-color: var(--color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 150px;
|
||||
transition: border-color .2s;
|
||||
color: var(--color-text-highlight);
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
height: 2rem;
|
||||
padding-inline: 0.5rem;
|
||||
border-radius: 0.3rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown-button:hover {
|
||||
.theme-choices .theme-preset::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -.4rem;
|
||||
border-radius: .7rem;
|
||||
border: 2px solid transparent;
|
||||
transition: border-color .3s;
|
||||
}
|
||||
|
||||
.theme-choices .theme-preset:hover::before {
|
||||
border-color: var(--color-text-subdue);
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background: var(--color-widget-content-border);
|
||||
min-width: 150px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
border-radius: 4px;
|
||||
z-index: 1000;
|
||||
.theme-choices .theme-preset.current::before {
|
||||
border-color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.mobile-navigation-page-links .dropdown-content {
|
||||
top: unset;
|
||||
bottom: 38px;
|
||||
.theme-preset-light {
|
||||
gap: 0.3rem;
|
||||
height: 1.8rem;
|
||||
}
|
||||
|
||||
.dropdown-content.show {
|
||||
display: block;
|
||||
.theme-color {
|
||||
background-color: var(--color);
|
||||
width: 0.9rem;
|
||||
height: 0.9rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
.theme-option {
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
.theme-preset-light .theme-color {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
.theme-option:hover {
|
||||
background-color: #f8f9fa;
|
||||
.current-theme-preview {
|
||||
opacity: 0.4;
|
||||
transition: opacity .3s;
|
||||
}
|
||||
|
||||
.separator {
|
||||
height: 1px;
|
||||
background-color: #dee2e6;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
border: solid #666;
|
||||
border-width: 0 2px 2px 0;
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
transform: rotate(45deg);
|
||||
transition: transform 0.2s ease;
|
||||
margin-left: auto;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.dropdown-button.active .arrow {
|
||||
transform: rotate(-135deg);
|
||||
.theme-picker.popover-active .current-theme-preview, .theme-picker:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ details[open] .summary::after {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
:root:not(.light-scheme) .flat-icon {
|
||||
:root:not([data-scheme=light]) .flat-icon {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
@@ -459,6 +459,23 @@ details[open] .summary::after {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
|
||||
.hide-scrollbars {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
/* Hide on Safari and Chrome */
|
||||
.hide-scrollbars::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ui-icon {
|
||||
width: 2.3rem;
|
||||
height: 2.3rem;
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.size-h1 { font-size: var(--font-size-h1); }
|
||||
.size-h2 { font-size: var(--font-size-h2); }
|
||||
.size-h3 { font-size: var(--font-size-h3); }
|
||||
@@ -510,6 +527,7 @@ details[open] .summary::after {
|
||||
.grow { flex-grow: 1; }
|
||||
.flex-column { flex-direction: column; }
|
||||
.items-center { align-items: center; }
|
||||
.self-center { align-self: center; }
|
||||
.items-start { align-items: start; }
|
||||
.items-end { align-items: end; }
|
||||
.gap-5 { gap: 0.5rem; }
|
||||
@@ -549,6 +567,7 @@ details[open] .summary::after {
|
||||
.padding-widget { padding: var(--widget-content-padding); }
|
||||
.padding-block-widget { padding-block: var(--widget-content-vertical-padding); }
|
||||
.padding-inline-widget { padding-inline: var(--widget-content-horizontal-padding); }
|
||||
.pointer-events-none { pointer-events: none; }
|
||||
.padding-block-5 { padding-block: 0.5rem; }
|
||||
.scale-half { transform: scale(0.5); }
|
||||
.list { --list-half-gap: 0rem; }
|
||||
|
||||
@@ -1,30 +1,7 @@
|
||||
import { setupPopovers } from './popover.js';
|
||||
import { setupMasonries } from './masonry.js';
|
||||
import { throttledDebounce, isElementVisible, openURLInNewTab } from './utils.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const theme = localStorage.getItem('theme');
|
||||
|
||||
if (!theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
const html = document.querySelector('html');
|
||||
const jsonTheme = JSON.parse(theme);
|
||||
if (jsonTheme.themeScheme === 'light') {
|
||||
html.classList.remove('dark-scheme');
|
||||
html.classList.add('light-scheme');
|
||||
} else if (jsonTheme.themeScheme === 'dark') {
|
||||
html.classList.add('dark-scheme');
|
||||
html.classList.remove('light-scheme');
|
||||
}
|
||||
|
||||
html.classList.add(jsonTheme.theme);
|
||||
document.querySelector('[name=color-scheme]').setAttribute('content', jsonTheme.themeScheme);
|
||||
Array.from(document.querySelectorAll('.dropdown-button span')).forEach((button) => {
|
||||
button.textContent = jsonTheme.theme;
|
||||
})
|
||||
})
|
||||
import { elem, find, findAll } from './templating.js';
|
||||
|
||||
async function fetchPageContent(pageData) {
|
||||
// TODO: handle non 200 status codes/time outs
|
||||
@@ -678,102 +655,77 @@ function setupTruncatedElementTitles() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} HslColorField
|
||||
* @property {number} Hue
|
||||
* @property {number} Saturation
|
||||
* @property {number} Lightness
|
||||
*/
|
||||
async function changeTheme(key, onChanged) {
|
||||
const themeStyleElem = find("#theme-style");
|
||||
|
||||
/**
|
||||
* @typedef {Object} Theme
|
||||
* @property {HslColorField} BackgroundColor
|
||||
* @property {HslColorField} PrimaryColor
|
||||
* @property {HslColorField} PositiveColor
|
||||
* @property {HslColorField} NegativeColor
|
||||
* @property {boolean} Light
|
||||
* @property {number} ContrastMultiplier
|
||||
* @property {number} TextSaturationMultiplier
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Record<string, Theme>} ThemeCollection
|
||||
*/
|
||||
function setupThemeSwitcher() {
|
||||
const presetsContainers = Array.from(document.querySelectorAll('.custom-presets'));
|
||||
const userThemesKeys = Object.keys(userThemes);
|
||||
|
||||
presetsContainers.forEach((presetsContainer) => {
|
||||
userThemesKeys.forEach(preset => {
|
||||
const presetElement = document.createElement('div');
|
||||
presetElement.className = 'theme-option';
|
||||
presetElement.setAttribute('data-theme', preset);
|
||||
presetElement.setAttribute('data-scheme', userThemes[preset].Light ? 'light' : 'dark');
|
||||
presetElement.textContent = preset;
|
||||
presetsContainer.appendChild(presetElement);
|
||||
});
|
||||
const response = await fetch(`${pageData.baseURL}/api/set-theme/${key}`, {
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
const dropdownButtons = Array.from(document.querySelectorAll('.dropdown-button'));
|
||||
const dropdownContents = Array.from(document.querySelectorAll('.dropdown-content'));
|
||||
if (response.status != 200) {
|
||||
alert("Failed to set theme: " + response.statusText);
|
||||
return;
|
||||
}
|
||||
const newThemeStyle = await response.text();
|
||||
|
||||
dropdownButtons.forEach((dropdownButton) => {
|
||||
dropdownButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
dropdownContents.forEach((dropdownContent) => {
|
||||
dropdownContent.classList.toggle('show');
|
||||
});
|
||||
dropdownButton.classList.toggle('active');
|
||||
});
|
||||
});
|
||||
const tempStyle = elem("style")
|
||||
.html("* { transition: none !important; }")
|
||||
.appendTo(document.head);
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.theme-dropdown')) {
|
||||
dropdownContents.forEach((dropdownContent) => {
|
||||
dropdownContent.classList.remove('show');
|
||||
});
|
||||
dropdownButtons.forEach((dropdownButton) => {
|
||||
dropdownButton.classList.remove('active');
|
||||
});
|
||||
themeStyleElem.html(newThemeStyle);
|
||||
document.documentElement.setAttribute("data-scheme", response.headers.get("X-Scheme"));
|
||||
typeof onChanged == "function" && onChanged();
|
||||
setTimeout(() => { tempStyle.remove(); }, 10);
|
||||
}
|
||||
|
||||
function initThemeSwitcher() {
|
||||
find(".mobile-navigation .theme-choices").replaceWith(
|
||||
find(".header-container .theme-choices").cloneNode(true)
|
||||
);
|
||||
|
||||
const presetElems = findAll(".theme-choices .theme-preset");
|
||||
let themePreviewElems = document.getElementsByClassName("current-theme-preview");
|
||||
let isLoading = false;
|
||||
|
||||
presetElems.forEach((presetElement) => {
|
||||
const themeKey = presetElement.dataset.key;
|
||||
|
||||
if (themeKey === undefined) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.theme-option').forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
const selectedTheme = option.getAttribute('data-theme');
|
||||
const selectedThemeScheme = option.getAttribute('data-scheme');
|
||||
const previousTheme = localStorage.getItem('theme');
|
||||
dropdownContents.forEach((dropdownContent) => {
|
||||
dropdownContent.classList.remove('show');
|
||||
});
|
||||
dropdownButtons.forEach((dropdownButton) => {
|
||||
const html = document.querySelector('html');
|
||||
if (previousTheme) {
|
||||
html.classList.remove(JSON.parse(previousTheme).theme);
|
||||
}
|
||||
dropdownButton.classList.remove('active');
|
||||
dropdownButton.querySelector('span').textContent = option.textContent;
|
||||
html.classList.add(selectedTheme);
|
||||
if (themeKey == pageData.theme) {
|
||||
presetElement.classList.add("current");
|
||||
}
|
||||
|
||||
if (selectedThemeScheme === 'light') {
|
||||
html.classList.remove('dark-scheme');
|
||||
html.classList.add('light-scheme');
|
||||
} else if (selectedThemeScheme === 'dark') {
|
||||
html.classList.add('dark-scheme');
|
||||
html.classList.remove('light-scheme');
|
||||
}
|
||||
presetElement.addEventListener("click", () => {
|
||||
if (themeKey == pageData.theme) return;
|
||||
if (isLoading) return;
|
||||
|
||||
document.querySelector('[name=color-scheme]').setAttribute('content', selectedThemeScheme);
|
||||
localStorage.setItem('theme', JSON.stringify({
|
||||
theme: selectedTheme,
|
||||
themeScheme: selectedThemeScheme
|
||||
}));
|
||||
isLoading = true;
|
||||
changeTheme(themeKey, function() {
|
||||
isLoading = false;
|
||||
pageData.theme = themeKey;
|
||||
presetElems.forEach((e) => { e.classList.remove("current"); });
|
||||
|
||||
Array.from(themePreviewElems).forEach((preview) => {
|
||||
preview.querySelector(".theme-preset").replaceWith(
|
||||
presetElement.cloneNode(true)
|
||||
);
|
||||
})
|
||||
|
||||
presetElems.forEach((e) => {
|
||||
if (e.dataset.key != themeKey) return;
|
||||
e.classList.add("current");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
async function setupPage() {
|
||||
initThemeSwitcher();
|
||||
|
||||
const pageElement = document.getElementById("page");
|
||||
const pageContentElement = document.getElementById("page-content");
|
||||
const pageContent = await fetchPageContent(pageData);
|
||||
@@ -781,7 +733,6 @@ async function setupPage() {
|
||||
pageContentElement.innerHTML = pageContent;
|
||||
|
||||
try {
|
||||
setupThemeSwitcher();
|
||||
setupPopovers();
|
||||
setupClocks()
|
||||
await setupCalendars();
|
||||
|
||||
@@ -37,9 +37,6 @@ export function setupMasonries() {
|
||||
columnsFragment.append(column);
|
||||
}
|
||||
|
||||
// poor man's masonry
|
||||
// TODO: add an option that allows placing items in the
|
||||
// shortest column instead of iterating the columns in order
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
columnsFragment.children[i % columnsCount].appendChild(items[i]);
|
||||
}
|
||||
|
||||
@@ -157,6 +157,9 @@ function hidePopover() {
|
||||
|
||||
activeTarget.classList.remove("popover-active");
|
||||
containerElement.style.display = "none";
|
||||
containerElement.style.removeProperty("top");
|
||||
containerElement.style.removeProperty("left");
|
||||
containerElement.style.removeProperty("right");
|
||||
document.removeEventListener("keydown", handleHidePopoverOnEscape);
|
||||
window.removeEventListener("resize", queueRepositionContainer);
|
||||
observer.unobserve(containerElement);
|
||||
@@ -181,7 +184,12 @@ export function setupPopovers() {
|
||||
for (let i = 0; i < targets.length; i++) {
|
||||
const target = targets[i];
|
||||
|
||||
target.addEventListener("mouseenter", handleMouseEnter);
|
||||
if (target.dataset.popoverTrigger === "click") {
|
||||
target.addEventListener("click", handleMouseEnter);
|
||||
} else {
|
||||
target.addEventListener("mouseenter", handleMouseEnter);
|
||||
}
|
||||
|
||||
target.addEventListener("mouseleave", handleMouseLeave);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user