diff options
| -rw-r--r-- | src/app.css | 10 | ||||
| -rw-r--r-- | src/lib/blog/Post.svelte | 16 | ||||
| -rw-r--r-- | src/lib/storage.ts | 1 | ||||
| -rw-r--r-- | src/lib/theme.svelte.ts | 49 | ||||
| -rw-r--r-- | src/routes/blog/+layout.svelte | 21 |
5 files changed, 75 insertions, 22 deletions
diff --git a/src/app.css b/src/app.css index c41d249..c522036 100644 --- a/src/app.css +++ b/src/app.css @@ -220,7 +220,7 @@ } /* - A Blog Sepia, very TODO + TODO: Sepia properly */ [data-blog-theme=sepia] { @apply bg-[#2b261e]; @@ -228,6 +228,14 @@ [data-blog-theme=light] { @apply invert hue-rotate-180; } +[data-blog-theme] { + transition-property: filter; + transition-timing-function: ease-in-out; + transition-duration: 0.4s; +} +:root:not([data-blog-theme]) .theme-selector { + display: none; +} /* The default border color has changed to `currentColor` in Tailwind CSS v4, diff --git a/src/lib/blog/Post.svelte b/src/lib/blog/Post.svelte index 9fcc319..05afa78 100644 --- a/src/lib/blog/Post.svelte +++ b/src/lib/blog/Post.svelte @@ -5,6 +5,7 @@ <script lang="ts"> import { building, dev } from '$app/environment'; import { page } from '$app/state'; + import theme from '../theme.svelte'; import { parsePostMetadata, type Post } from './Post'; @@ -43,9 +44,8 @@ </p> {#if !ignorePublishedStatus && dev} <p class="mt-1 5"> - <a - href={new URL('?ignore-unpublished=+', page.url).href} - class="quicklink">Ignore and read anyway</a + <a href="?ignore-unpublished" class="quicklink" + >Ignore and read anyway</a > </p> {:else if ignorePublishedStatus} @@ -73,7 +73,15 @@ class="quicklink" target="_blank" rel="noopener noreferrer">src</a - >{/if}<span class="select-none">▒</span>ctime: {meta.created + >{/if}<span class="select-none">▒</span><a + href="?theme={theme.opposite}" + class="quicklink theme-selector" + onclick={(e) => { + theme.flip(); + e.currentTarget.blur(); + e.preventDefault(); + }}>toggle theme</a + ><span class="select-none theme-selector">▒</span>ctime: {meta.created .toISOString() .split('T')[0]}<span class="select-none">▒</span>mtime: {meta.updated .toISOString() diff --git a/src/lib/storage.ts b/src/lib/storage.ts index 0a5334d..48eef3c 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -5,3 +5,4 @@ export const sessionStorage = new StorageManager('session', 'mem.estrogen.zone:' export const memoryStorage = new StorageManager('memory', 'mem.estrogen.zone:'); export const blogStorage = new StorageManager(localStorage, 'blog:'); +export const themeStorage = new StorageManager(localStorage, 'theme:'); diff --git a/src/lib/theme.svelte.ts b/src/lib/theme.svelte.ts new file mode 100644 index 0000000..956738b --- /dev/null +++ b/src/lib/theme.svelte.ts @@ -0,0 +1,49 @@ +import { onDestroy, onMount } from 'svelte'; +import { themeStorage } from './storage'; +import { page } from '$app/state'; + +export enum Theme { + Light = 'light', + Dark = 'dark', +} +const oppositeThemeMappings: Record<Theme, Theme> = { + [Theme.Light]: Theme.Dark, + [Theme.Dark]: Theme.Light +}; +export class CTheme { + public rawTheme = $state(null as null | Theme); + public theme = $derived(this.rawTheme ?? Theme.Dark); + public opposite = $derived(oppositeThemeMappings[this.theme]); + /** + * Marks the page a component renders on as fully compatible/tested with theming. + * + * Call during component init. + */ + public themeCompatible() { + onMount(() => { + const t = + page.url.searchParams.get('theme') ?? themeStorage.getItem('theme'); + if (theme.isThemeValid(t)) theme.set(t); + }); + $effect(() => { + document.documentElement.setAttribute('data-blog-theme', theme.theme); + page.url.searchParams.delete('theme'); + }); + onDestroy(() => { + if (typeof document !== 'undefined') + document.documentElement.removeAttribute('data-blog-theme'); + }); + } + public isThemeValid(theme: string | null): theme is Theme { + return Object.values(Theme).includes(theme as unknown as Theme); + } + public set(theme: Theme) { + themeStorage.setItem('theme', theme); + this.theme = theme; + } + public flip() { + this.set(this.opposite); + } +}; +export const theme = new CTheme(); +export default theme; diff --git a/src/routes/blog/+layout.svelte b/src/routes/blog/+layout.svelte index 61ab5f3..073d5c3 100644 --- a/src/routes/blog/+layout.svelte +++ b/src/routes/blog/+layout.svelte @@ -1,27 +1,14 @@ <script lang="ts"> - import { blogStorage } from '$/lib/storage'; - import { page } from '$app/state'; - import { onDestroy, onMount, type Snippet } from 'svelte'; + import theme from '$/lib/theme.svelte'; + import { type Snippet } from 'svelte'; + + theme.themeCompatible(); const { children, }: { children: Snippet; } = $props(); - - onMount(() => { - const theme = - page.url.searchParams.get('theme') ?? blogStorage.getItem('theme'); - if (theme) { - blogStorage.setItem('theme', theme); - document.documentElement.setAttribute('data-blog-theme', theme); - page.url.searchParams.delete('theme'); - } - }); - onDestroy(() => { - if (typeof document !== 'undefined') - document.documentElement.removeAttribute('data-blog-theme'); - }); </script> {@render children()} |