Skip to content

Commit d73fc9d

Browse files
committed
Updates
1 parent 144b257 commit d73fc9d

21 files changed

Lines changed: 711 additions & 250 deletions

about.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
<html lang="en">
33

44
<head>
5+
<script>
6+
(function () {
7+
var stored = localStorage.getItem('theme');
8+
var prefers = window.matchMedia('(prefers-color-scheme:dark)').matches;
9+
if (stored === 'dark' || (!stored && prefers)) {
10+
document.documentElement.classList.add('dark-mode');
11+
}
12+
})();
13+
</script>
514
<meta charset="UTF-8" />
615
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
716
<title>About - Robin's Site</title>

api/comments.js

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,70 @@
1-
// /api/comments.js
2-
3-
import fs from "fs";
4-
import path from "path";
51
import { kv } from "@vercel/kv";
62
import sanitizeHtml from "sanitize-html";
3+
import fs from "fs/promises";
4+
import path from "path";
75

8-
const USE_KV = process.env.USE_KV === "true";
9-
10-
function filePath() { return path.join(process.cwd(), "comments.json"); }
6+
const USE_KV = process.env.VERCEL_ENV === "production";
7+
const COMMENTS_DIR = path.join(process.cwd(), "comments");
118

9+
// Always use KV—remove file‐based fallback to avoid race conditions
1210
async function readComments(slug) {
13-
if (USE_KV) return (await kv.get(`comments:${slug}`)) || [];
14-
if (!fs.existsSync(filePath())) fs.writeFileSync(filePath(), "{}");
15-
const data = JSON.parse(fs.readFileSync(filePath(), "utf8"));
16-
return data[slug] || [];
11+
if (USE_KV) {
12+
return (await kv.get(`comments:${slug}`)) || [];
13+
}
14+
try {
15+
const file = path.join(COMMENTS_DIR, `${slug}.json`);
16+
const raw = await fs.readFile(file, "utf8");
17+
return JSON.parse(raw);
18+
} catch {
19+
return [];
20+
}
1721
}
1822

1923
async function writeComments(slug, arr) {
20-
if (USE_KV) return kv.set(`comments:${slug}`, arr);
21-
const data = fs.existsSync(filePath())
22-
? JSON.parse(fs.readFileSync(filePath(), "utf8"))
23-
: {};
24-
data[slug] = arr;
25-
fs.writeFileSync(filePath(), JSON.stringify(data, null, 2));
24+
if (USE_KV) {
25+
await kv.set(`comments:${slug}`, arr);
26+
return;
27+
}
28+
await fs.mkdir(COMMENTS_DIR, { recursive: true });
29+
const file = path.join(COMMENTS_DIR, `${slug}.json`);
30+
await fs.writeFile(file, JSON.stringify(arr, null, 2), "utf8");
2631
}
2732

2833
export default async function handler(req, res) {
29-
if (req.method === 'POST' && !req.body) {
30-
return res.status(400).json({ error: "Invalid or missing request body" });
31-
}
34+
if (req.method === 'POST' && !req.body) {
35+
return res.status(400).json({ error: "Invalid or missing request body" });
36+
}
3237

33-
if (req.method === 'GET') {
34-
const { slug } = req.query;
35-
if (!slug) {
36-
return res.status(400).json({ error: "Missing slug" });
37-
}
38-
const comments = await readComments(slug);
39-
return res.status(200).json(comments);
40-
}
38+
if (req.method === 'GET') {
39+
const { slug } = req.query;
40+
if (!slug) return res.status(400).json({ error: "Missing slug" });
41+
const comments = await readComments(slug);
42+
return res.status(200).json(comments);
43+
}
4144

42-
if (req.method === 'POST') {
43-
const { slug, text } = req.body;
44-
if (!slug || !text) {
45-
return res.status(400).json({ error: "Missing slug or text" });
46-
}
47-
48-
const sanitizedText = sanitizeHtml(text, {
49-
allowedTags: [],
50-
allowedAttributes: {},
51-
});
52-
53-
const timestamp = Date.now();
54-
const comments = await readComments(slug);
55-
comments.push({ text: sanitizedText, timestamp });
56-
await writeComments(slug, comments);
57-
return res.status(201).json({ text: sanitizedText, timestamp });
45+
if (req.method === 'POST') {
46+
const { slug, text, author } = req.body;
47+
if (!slug || !text) {
48+
return res.status(400).json({ error: "Missing slug or text" });
5849
}
5950

60-
res.setHeader('Allow', ['GET', 'POST']);
61-
res.status(405).end(`Method ${req.method} Not Allowed`);
62-
}
51+
const sanitizedText = sanitizeHtml(text, {
52+
allowedTags: [],
53+
allowedAttributes: {},
54+
});
55+
const sanitizedAuthor = sanitizeHtml(author || "Anonymous", {
56+
allowedTags: [],
57+
allowedAttributes: {},
58+
});
59+
const timestamp = new Date();
60+
61+
const comments = await readComments(slug);
62+
comments.push({ author: sanitizedAuthor, text: sanitizedText, timestamp });
63+
await writeComments(slug, comments);
64+
65+
return res.status(201).json({ author: sanitizedAuthor, text: sanitizedText, timestamp });
66+
}
67+
68+
res.setHeader('Allow', ['GET', 'POST']);
69+
res.status(405).end(`Method ${req.method} Not Allowed`);
70+
}

api/github-languages.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import fetch from "node-fetch";
2+
3+
export default async function handler(req, res) {
4+
const { repo } = req.query;
5+
if (!repo) {
6+
return res.status(400).json({ error: "Missing repo query parameter" });
7+
}
8+
9+
const token = process.env.GITHUB_PAT || '';
10+
const headers = token
11+
? { Authorization: `token ${token}` }
12+
: {};
13+
14+
try {
15+
const gh = await fetch(`https://api.github.com/repos/${repo}/languages`, { headers });
16+
if (!gh.ok) {
17+
return res.status(gh.status).json({ error: "GitHub API error" });
18+
}
19+
const data = await gh.json();
20+
return res.status(200).json(data);
21+
} catch (err) {
22+
console.error("Proxy error:", err);
23+
return res.status(500).json({ error: "Internal error" });
24+
}
25+
}

blog.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
<html lang="en">
33

44
<head>
5+
<script>
6+
(function () {
7+
var stored = localStorage.getItem('theme');
8+
var prefers = window.matchMedia('(prefers-color-scheme:dark)').matches;
9+
if (stored === 'dark' || (!stored && prefers)) {
10+
document.documentElement.classList.add('dark-mode');
11+
}
12+
})();
13+
</script>
514
<meta charset="UTF-8" />
615
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
716
<title>Blog – Robin's Site</title>

components/footer.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
<footer>
2-
<small style="text-align:center; font-size:0.9rem; color:#777; display:block;">&copy; 2025 Robin. All rights reserved.</small>
2+
<small style="text-align:center; font-size:0.9rem; color:#777; display:block;">
3+
&copy; <span id="current-year"></span> Robin. All rights reserved.
4+
</small>
35
</footer>

components/header.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
<div class="header-title">Robin's Site</div>
2-
<nav>
1+
<div class="header-title">Robin's Site ~/</div>
2+
<nav>
33
<a href="index.html">Home</a> |
44
<a href="about.html">About</a> |
55
<a href="blog.html">Blog</a> | <!-- points to the new listing page -->
66
<a href="projects.html">Projects</a>
77
</nav>
88
<div class="header-row">
99
<p>Welcome! Sharing ideas, projects, school notes, and more.</p>
10-
<button id="theme-toggle">Dark Mode</button>
10+
<button id="theme-toggle" aria-label="Toggle Dark Mode">Dark Mode</button>
1111
</div>

css/blog.css

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,28 @@
1111
}
1212

1313
/* Individual blog post entries */
14-
.post-item {
14+
.post-entry {
1515
margin-bottom: 2rem;
1616
padding-bottom: 1rem;
1717
border-bottom: 1px solid #ddd;
1818
}
1919

20-
.dark-mode .post-item {
20+
.dark-mode .post-entry {
2121
border-color: #444;
2222
}
2323

2424
/* Title & Meta */
25-
.post-item h2 {
25+
.post-entry h2 {
2626
margin: 0 0 0.25rem;
2727
font-size: 1.5rem;
2828
}
2929

30-
.post-item a {
30+
.post-entry a {
3131
color: var(--link-color, #0055aa);
3232
text-decoration: none;
3333
}
3434

35-
.post-item a:hover {
35+
.post-entry a:hover {
3636
text-decoration: underline;
3737
}
3838

@@ -161,16 +161,16 @@
161161
}
162162

163163
/* Pagination buttons */
164-
#pagination-controls {
164+
#pagination {
165165
display: flex;
166166
justify-content: flex-end;
167167
gap: 1rem;
168168
margin-top: 2rem;
169169
}
170170

171171
/* Match the theme-toggle style for pagination buttons */
172-
#prev-page,
173-
#next-page {
172+
#prev-button,
173+
#next-button {
174174
font-family: inherit;
175175
font-size: 1rem;
176176
padding: 0.5rem 1rem;
@@ -181,31 +181,31 @@
181181
cursor: pointer;
182182
}
183183

184-
#prev-page:hover,
185-
#next-page:hover {
184+
#prev-button:hover,
185+
#next-button:hover {
186186
background-color: #88bbff;
187187
}
188188

189-
#prev-page:disabled,
190-
#next-page:disabled {
189+
#prev-button:disabled,
190+
#next-button:disabled {
191191
background-color: #ccc;
192192
cursor: not-allowed;
193193
}
194194

195195
/* Dark mode: match theme-toggle hover */
196-
.dark-mode #prev-page,
197-
.dark-mode #next-page {
196+
.dark-mode #prev-button,
197+
.dark-mode #next-button {
198198
background-color: #333;
199199
color: #eee;
200200
}
201201

202-
.dark-mode #prev-page:hover,
203-
.dark-mode #next-page:hover {
202+
.dark-mode #prev-button:hover,
203+
.dark-mode #next-button:hover {
204204
background-color: #666;
205205
}
206206

207-
.dark-mode #prev-page:disabled,
208-
.dark-mode #next-page:disabled {
207+
.dark-mode #prev-button:disabled,
208+
.dark-mode #next-button:disabled {
209209
background-color: #555;
210210
cursor: not-allowed;
211211
}

css/projects.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,19 @@
7575
white-space: nowrap;
7676
font-size: 0.9rem;
7777
color: #555;
78+
}
79+
80+
.linguist-bar {
81+
display: flex;
82+
width: 100%;
83+
height: 8px;
84+
margin: 0.5rem 0;
85+
overflow: hidden;
86+
border-radius: 4px;
87+
background-color: #ddd;
88+
}
89+
90+
.linguist-bar-segment {
91+
display: inline-block;
92+
height: 100%;
7893
}

css/shared.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ textarea {
154154
border: none;
155155
margin-bottom: 1rem;
156156
background-color: #eeeeee;
157-
color: #bbbbbb;
157+
color: #666666;
158158
}
159159

160160
textarea {

index.html

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
<html lang="en">
33

44
<head>
5+
<script>
6+
(function () {
7+
var stored = localStorage.getItem('theme');
8+
var prefers = window.matchMedia('(prefers-color-scheme:dark)').matches;
9+
if (stored === 'dark' || (!stored && prefers)) {
10+
document.documentElement.classList.add('dark-mode');
11+
}
12+
})();
13+
</script>
514
<meta charset="UTF-8" />
615
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
716
<title>Robin's Site</title>
@@ -14,10 +23,10 @@
1423
style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;
1524
font-src 'self' https://cdn.jsdelivr.net;
1625
img-src data:;" charset="UTF-8">
26+
</head>
1727

1828
<body>
1929
<header id="header-placeholder"></header>
20-
<div class="header-title">~</div>
2130
<div class="header-row">
2231
<p>This is where I share random ideas and stuff I created about school, life, etc. (Not any type of
2332
category that I want to fall into, journal, diary, because it would feel kind of restrictive on 'how'/'what'

0 commit comments

Comments
 (0)