Skip to content

Commit 5e8b3b7

Browse files
committed
feat: SSR theming with cloudflare workers
1 parent a2385b3 commit 5e8b3b7

File tree

16 files changed

+2014
-63
lines changed

16 files changed

+2014
-63
lines changed

src/components/BaseHead.astro

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const { title, description, themeColor } = Astro.props;
2424
}
2525

2626
function getColorMode() {
27-
const preferenceRaw = localStorage.getItem(THEME_KEY);
27+
const preferenceRaw = document.cookie.match(/theme=(.+);?/)?.[1];
2828
const isValid = preferenceRaw && isMode(preferenceRaw);
2929

3030
let preference = isValid ? preferenceRaw : DEFAULT_THEME_MODE;
@@ -42,11 +42,18 @@ const { title, description, themeColor } = Astro.props;
4242
} else {
4343
document.documentElement.classList.remove("dark");
4444
}
45-
localStorage.setItem(THEME_KEY, theme);
45+
document.cookie = `${THEME_KEY}=${theme}; Path=/; Max-Age=31536000`;
4646
}
4747

4848
function setThemeDisplay(mode) {
49-
setKnownTheme(mode === "system" ? "light" : mode);
49+
if (mode === "system") {
50+
const shouldBeDark = window.matchMedia(
51+
"(prefers-color-scheme: dark)",
52+
).matches;
53+
setKnownTheme(shouldBeDark ? "dark" : "light");
54+
} else {
55+
setKnownTheme(mode);
56+
}
5057
}
5158

5259
function addThemeChangeListener(f) {

src/components/tv/watched-tv-show.tsx

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,62 @@ import cls from "clsx";
22
import type { meQuery } from "🛠️/me";
33

44
export interface Props {
5-
show: Awaited<typeof meQuery>["tv"][number];
6-
selected?: number;
7-
i?: number;
5+
show: Awaited<typeof meQuery>["tv"][number];
6+
selected?: number;
7+
i?: number;
88
}
99

1010
function parseEpisode(text: string) {
11-
return {
12-
season: /S0?(\d+)/.exec(text)?.[1],
13-
episode: /E0?(\d+)/.exec(text)?.[1],
14-
};
11+
return {
12+
season: /S0?(\d+)/.exec(text)?.[1],
13+
episode: /E0?(\d+)/.exec(text)?.[1],
14+
};
1515
}
1616

1717
export default function WatchedTvShow({ show, selected, i }: Props) {
18-
const { episode, season } = parseEpisode(show.episode);
19-
if (!show.coverUrl) {
20-
throw new Error("Invalid coverUrl, empty");
21-
}
18+
const { episode, season } = parseEpisode(show.episode);
19+
if (!show.coverUrl) {
20+
throw new Error("Invalid coverUrl, empty");
21+
}
2222

23-
const isSelected = selected === i || typeof selected === "undefined";
24-
return (
25-
<a
26-
href={show.simklLink ?? "#"}
27-
rel="nofollower noopener external"
28-
className={cls("flex flex-col gap-1 w-full h-full")}
29-
>
30-
<img
31-
src={show.coverUrl}
32-
alt={`Cover for ${show.title}`}
33-
className={cls(
34-
"rounded mb-2 max-h-[200px] object-cover h-full w-full ease-out transition-all aspect-ratio-[9/16]",
35-
isSelected ? "opacity-100" : "opacity-20%",
36-
)}
37-
/>
38-
<div className="flex flex-col gap-1">
39-
<h3
40-
className={cls(
41-
"font-medium whitespace-nowrap overflow-hidden",
42-
isSelected ? "color-text-800" : "color-text-100 opacity-50%",
43-
)}
44-
>
45-
{show.title}
46-
</h3>
47-
<span
48-
className={cls(
49-
"text-xs flex gap-1",
50-
isSelected ? "color-text-300" : "color-text-100 opacity-50%",
51-
)}
52-
>
53-
{season !== undefined && <p>S{season}</p>}
54-
{episode !== undefined && (
55-
<>
56-
{season && "·"} <p>E{episode}</p>
57-
</>
58-
)}
59-
</span>
60-
</div>
61-
</a>
62-
);
23+
const isSelected = selected === i || typeof selected === "undefined";
24+
return (
25+
<a
26+
href={show.simklLink ?? "#"}
27+
rel="nofollower noopener external"
28+
className={cls("flex flex-col gap-1 w-full h-full")}
29+
>
30+
<img
31+
src={show.coverUrl}
32+
alt={`Cover for ${show.title}`}
33+
className={cls(
34+
"rounded mb-2 max-h-[200px] object-cover h-full w-full ease-out transition-all aspect-ratio-[9/16]",
35+
isSelected ? "opacity-100" : "opacity-20%"
36+
)}
37+
/>
38+
<div className="flex flex-col gap-1">
39+
<h3
40+
className={cls(
41+
"font-medium whitespace-nowrap overflow-hidden",
42+
isSelected ? "color-text-800" : "color-text-100 opacity-50%"
43+
)}
44+
>
45+
{show.title}
46+
</h3>
47+
<span
48+
className={cls(
49+
"text-xs flex gap-1",
50+
isSelected ? "color-text-300" : "color-text-100 opacity-50%"
51+
)}
52+
>
53+
{season !== undefined && <p>S{season}</p>}
54+
{episode !== undefined && (
55+
<>
56+
{season && "·"} <p>E{episode}</p>
57+
</>
58+
)}
59+
</span>
60+
</div>
61+
</a>
62+
);
6363
}

src/scripts/theme-switcher.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@ const modes = ["dark", "light", "system"] as const;
33
const DEFAULT_THEME_MODE = "system";
44
const THEME_KEY = "theme";
55

6-
export type Mode = typeof modes[number];
6+
export type Mode = (typeof modes)[number];
77

88
function isMode(input: string): input is Mode {
99
return modes.includes(input as Mode);
1010
}
1111

1212
export function getColorMode(): Mode {
13-
const preferenceRaw = localStorage.getItem(THEME_KEY);
13+
const preferenceRaw = document.cookie.match(/theme=(.+);?/)?.[1];
1414
const isValid = preferenceRaw && isMode(preferenceRaw);
1515

16-
let preference = isValid ? preferenceRaw : DEFAULT_THEME_MODE;
16+
const preference = isValid ? preferenceRaw : DEFAULT_THEME_MODE;
1717

1818
if (!isValid) {
1919
setThemeDisplay(DEFAULT_THEME_MODE);
@@ -28,10 +28,7 @@ function setKnownTheme(theme: "dark" | "light") {
2828
} else {
2929
document.documentElement.classList.remove("dark");
3030
}
31-
}
32-
33-
export function persistTheme(mode: Mode): void {
34-
localStorage.setItem(THEME_KEY, mode);
31+
document.cookie = `${THEME_KEY}=${theme}; Path=/; Max-Age=31536000`;
3532
}
3633

3734
export function setThemeDisplay(mode: Mode): void {
@@ -42,7 +39,6 @@ export function setThemeDisplay(mode: Mode): void {
4239
setKnownTheme(shouldBeDark ? "dark" : "light");
4340
} else {
4441
setKnownTheme(mode);
45-
persistTheme(mode);
4642
}
4743
}
4844

theming/.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# http://editorconfig.org
2+
root = true
3+
4+
[*]
5+
indent_style = tab
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true
10+
11+
[*.yml]
12+
indent_style = space

theming/.gitignore

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# Logs
2+
3+
logs
4+
_.log
5+
npm-debug.log_
6+
yarn-debug.log*
7+
yarn-error.log*
8+
lerna-debug.log*
9+
.pnpm-debug.log*
10+
11+
# Diagnostic reports (https://nodejs.org/api/report.html)
12+
13+
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
14+
15+
# Runtime data
16+
17+
pids
18+
_.pid
19+
_.seed
20+
\*.pid.lock
21+
22+
# Directory for instrumented libs generated by jscoverage/JSCover
23+
24+
lib-cov
25+
26+
# Coverage directory used by tools like istanbul
27+
28+
coverage
29+
\*.lcov
30+
31+
# nyc test coverage
32+
33+
.nyc_output
34+
35+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
36+
37+
.grunt
38+
39+
# Bower dependency directory (https://bower.io/)
40+
41+
bower_components
42+
43+
# node-waf configuration
44+
45+
.lock-wscript
46+
47+
# Compiled binary addons (https://nodejs.org/api/addons.html)
48+
49+
build/Release
50+
51+
# Dependency directories
52+
53+
node_modules/
54+
jspm_packages/
55+
56+
# Snowpack dependency directory (https://snowpack.dev/)
57+
58+
web_modules/
59+
60+
# TypeScript cache
61+
62+
\*.tsbuildinfo
63+
64+
# Optional npm cache directory
65+
66+
.npm
67+
68+
# Optional eslint cache
69+
70+
.eslintcache
71+
72+
# Optional stylelint cache
73+
74+
.stylelintcache
75+
76+
# Microbundle cache
77+
78+
.rpt2_cache/
79+
.rts2_cache_cjs/
80+
.rts2_cache_es/
81+
.rts2_cache_umd/
82+
83+
# Optional REPL history
84+
85+
.node_repl_history
86+
87+
# Output of 'npm pack'
88+
89+
\*.tgz
90+
91+
# Yarn Integrity file
92+
93+
.yarn-integrity
94+
95+
# dotenv environment variable files
96+
97+
.env
98+
.env.development.local
99+
.env.test.local
100+
.env.production.local
101+
.env.local
102+
103+
# parcel-bundler cache (https://parceljs.org/)
104+
105+
.cache
106+
.parcel-cache
107+
108+
# Next.js build output
109+
110+
.next
111+
out
112+
113+
# Nuxt.js build / generate output
114+
115+
.nuxt
116+
dist
117+
118+
# Gatsby files
119+
120+
.cache/
121+
122+
# Comment in the public line in if your project uses Gatsby and not Next.js
123+
124+
# https://nextjs.org/blog/next-9-1#public-directory-support
125+
126+
# public
127+
128+
# vuepress build output
129+
130+
.vuepress/dist
131+
132+
# vuepress v2.x temp and cache directory
133+
134+
.temp
135+
.cache
136+
137+
# Docusaurus cache and generated files
138+
139+
.docusaurus
140+
141+
# Serverless directories
142+
143+
.serverless/
144+
145+
# FuseBox cache
146+
147+
.fusebox/
148+
149+
# DynamoDB Local files
150+
151+
.dynamodb/
152+
153+
# TernJS port file
154+
155+
.tern-port
156+
157+
# Stores VSCode versions used for testing VSCode extensions
158+
159+
.vscode-test
160+
161+
# yarn v2
162+
163+
.yarn/cache
164+
.yarn/unplugged
165+
.yarn/build-state.yml
166+
.yarn/install-state.gz
167+
.pnp.\*
168+
169+
# wrangler project
170+
171+
.dev.vars
172+
.wrangler/

theming/.prettierrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"printWidth": 140,
3+
"singleQuote": true,
4+
"semi": true,
5+
"useTabs": true
6+
}

0 commit comments

Comments
 (0)