Skip to content

Commit

Permalink
feat: cookie color schemes for rewriting html
Browse files Browse the repository at this point in the history
Now a middleware rewrites the HTML to apply the user preferred color scheme before the page loads, preventing a flash of the wrong color scheme.

The _routes.json file tells Cloudflare Functions which routes to work on, preventing the middleware from running on non-html routes and wasting function invocations.
  • Loading branch information
joaocstro committed Mar 20, 2023
1 parent 05e7ec2 commit 38a20b5
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 22 deletions.
24 changes: 24 additions & 0 deletions functions/_middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { parse } from 'cookie';

export const COLOR_SCHEME_COOKIE = 'colorScheme';
export const COLOR_SCHEME_BODY_ATTRIBUTE = 'data-color-scheme';
export type ColorSchemeValue = 'light' | 'dark';

export const onRequest: PagesFunction = async (context) => {
const cookie = parse(context.request.headers.get('Cookie') ?? '');
const colorScheme = cookie[COLOR_SCHEME_COOKIE];

class ElementHandler {
element(element: Element) {
if(element.tagName === 'body') {
element.setAttribute(COLOR_SCHEME_BODY_ATTRIBUTE, colorScheme);
}
}
}

if(colorScheme === 'light' || colorScheme === 'dark') {
return new HTMLRewriter().on('body', new ElementHandler()).transform(await context.next());
}

return context.next();
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@astrojs/svelte": "^2.0.0",
"astro": "^2.0.0",
"astro-critters": "^1.1.26",
"cookie": "^0.5.0",
"dayjs": "^1.11.7",
"fastest-levenshtein": "^1.0.16",
"miniflare": "^2.12.0",
Expand All @@ -26,6 +27,7 @@
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20230214.0",
"@types/cookie": "^0.5.1",
"typescript": "^4.9.5",
"wrangler": "^2.8.1"
}
Expand Down
5 changes: 5 additions & 0 deletions public/_routes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": 1,
"include": ["/api/*", "/", "/article/*"],
"exclude": []
}
58 changes: 36 additions & 22 deletions src/components/ColorSchemeToggle.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,66 @@
import { onMount } from "svelte";
import { scale } from "svelte/transition";
import { backOut } from "svelte/easing";
import { COLOR_SCHEME_COOKIE, COLOR_SCHEME_BODY_ATTRIBUTE } from "../../functions/_middleware";
import type { ColorSchemeValue } from "../../functions/_middleware";
type ColorScheme = "light" | "dark";
let nativeColorScheme: ColorSchemeValue;
let colorScheme: ColorSchemeValue;
const localStorageKey = "colorScheme";
const bodySchemeAttribute = "data-color-scheme";
let nativeColorScheme: ColorScheme;
let colorScheme: ColorScheme;
function getCookieColorScheme(): ColorSchemeValue | undefined {
const cookieColorScheme = document.cookie
.split(";")
.find((item) => item.includes(`${COLOR_SCHEME_COOKIE}=`))
?.match(new RegExp(`(?:^|; )${COLOR_SCHEME_COOKIE}=([^;]*)`))
?.at(1);
if(cookieColorScheme === 'light' || cookieColorScheme === 'dark') {
return cookieColorScheme;
}
}
function setCookieColorScheme(value: ColorSchemeValue, maxAge: number) {
document.cookie = `${COLOR_SCHEME_COOKIE}=${value};path=/;max-age=${maxAge};`;
}
function toggleColorScheme() {
colorScheme = colorScheme === "dark" ? "light" : "dark";
if(colorScheme === nativeColorScheme) {
localStorage.removeItem(localStorageKey);
document.body.removeAttribute(bodySchemeAttribute);
if (colorScheme === nativeColorScheme) {
setCookieColorScheme(colorScheme, 0);
document.body.removeAttribute(COLOR_SCHEME_BODY_ATTRIBUTE);
} else {
localStorage.setItem(localStorageKey, colorScheme);
document.body.setAttribute(bodySchemeAttribute, colorScheme);
setCookieColorScheme(colorScheme, 60 * 60 * 24 * 365);
document.body.setAttribute(COLOR_SCHEME_BODY_ATTRIBUTE, colorScheme);
}
}
onMount(() => {
let localStorageScheme = localStorage.getItem(localStorageKey);
let matchMedia = window.matchMedia("(prefers-color-scheme: dark)");
let cookieColorScheme = getCookieColorScheme();
nativeColorScheme = matchMedia.matches ? "dark" : "light";
matchMedia.addEventListener('change', event => {
if (cookieColorScheme === "light" || cookieColorScheme === "dark") {
colorScheme = cookieColorScheme;
document.body.setAttribute(COLOR_SCHEME_BODY_ATTRIBUTE, cookieColorScheme);
} else {
colorScheme = nativeColorScheme;
}
matchMedia.addEventListener("change", (event) => {
nativeColorScheme = event.matches ? "dark" : "light";
if(!localStorage.getItem(localStorageKey)) {
if (!getCookieColorScheme()) {
colorScheme = nativeColorScheme;
}
});
if (localStorageScheme === "light" || localStorageScheme === "dark") {
colorScheme = localStorageScheme;
document.body.setAttribute(bodySchemeAttribute, colorScheme);
} else {
colorScheme = nativeColorScheme;
}
});
</script>

<button on:click={toggleColorScheme} aria-label="Alternar esquema de cores">
{#if colorScheme === "dark"}
<svg
in:scale={{easing: backOut, duration: 650}}
in:scale={{ easing: backOut, duration: 650 }}
width="30"
height="30"
viewBox="0 0 30 30"
Expand All @@ -68,7 +82,7 @@
</svg>
{:else if colorScheme === "light"}
<svg
in:scale={{easing: backOut, duration: 650}}
in:scale={{ easing: backOut, duration: 650 }}
width="30"
height="30"
viewBox="0 0 20 20"
Expand Down

0 comments on commit 38a20b5

Please sign in to comment.