Merge pull request #17 from ghall89/big-changes

big changes
This commit is contained in:
Graham Hall
2025-02-13 09:54:13 -05:00
committed by GitHub
32 changed files with 6347 additions and 131 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,5 +1,5 @@
{
"_variables": {
"lastUpdateCheck": 1738292575979
"lastUpdateCheck": 1739331457547
}
}
+12 -7
View File
@@ -3,12 +3,17 @@ import { defineConfig } from 'astro/config';
// https://astro.build/config
import mdx from '@astrojs/mdx';
import alpinejs from '@astrojs/alpinejs';
// https://astro.build/config
export default defineConfig({
site: 'https://ghall.blog',
output: 'static',
integrations: [mdx()],
experimental: {
svg: true,
},
});
site: 'https://ghall.blog',
output: 'static',
integrations: [mdx(), alpinejs()],
experimental: {
svg: true,
},
redirects: {
'/posts/[...slug]': '/blog/[...slug]',
},
});
+6042
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -12,9 +12,12 @@
"lint": "eslint --ext .js --ext .ts --ext .astro"
},
"dependencies": {
"@astrojs/alpinejs": "^0.4.3",
"@astrojs/mdx": "4.0.2",
"@astrojs/rss": "4.0.10",
"@types/alpinejs": "^3.13.11",
"@types/markdown-it": "^14.1.2",
"alpinejs": "^3.14.8",
"astro": "^5.1.8",
"astro-pagefind": "^1.7.0",
"date-fns": "^4.1.0",
+30
View File
@@ -0,0 +1,30 @@
---
import { Image } from 'astro:assets';
import portrait from '../img/portrait.jpg';
interface Props {
size: number;
}
const { size } = Astro.props;
---
<Image
src={portrait}
alt="Illustrated portrait of a person with glasses and a beard, wearing a red and black plaid shirt. The background is a solid green color."
class="portrait"
width={`${size}`}
height={`${size}`}
/>
<style>
.portrait {
display: block;
margin: auto;
border-radius: 200px;
}
a > svg {
transform: translateY(0.18rem);
}
</style>
+4 -4
View File
@@ -1,12 +1,12 @@
---
import { format, add } from 'date-fns';
import CalendarIcon from '../styles/svg/calendar.svg';
import CalendarIcon from '../img/svg/calendar.svg';
interface Props {
title: String;
title: string;
date: Date;
slug?: String;
slug?: string;
}
const { title, date, slug } = Astro.props;
@@ -14,7 +14,7 @@ const { title, date, slug } = Astro.props;
<div class="blog-header">
<h2>
{slug ? <a href={`/posts/${slug}`}>{title}</a> : title}
{slug ? <a href={`/blog/${slug}`}>{title}</a> : title}
</h2>
<div>
<CalendarIcon class="calendar-icon" size={24} />
+2 -2
View File
@@ -1,11 +1,11 @@
---
import RssIcon from '../styles/svg/rss.svg';
import RssIcon from '../img/svg/rss.svg';
const year = new Date().getFullYear();
---
<footer>
<p>
Copyright {year}, Graham Hall
Copyright 2022 - {year}, Graham Hall
<br />
Built with <a href="https://astro.build">Astro</a>
+28 -9
View File
@@ -1,35 +1,54 @@
---
import Avatar from './Avatar.astro';
const { pathname } = Astro.url;
interface Props {}
interface NavLink {
label: string;
icon: string;
path: string;
}
const navLinks: NavLink[] = [
{ label: 'Blog', icon: 'pen', path: '/' },
{ label: 'About', icon: 'person', path: '/about/' },
{ label: 'Now', icon: 'clock', path: '/now/' },
{ label: 'Blog', path: 'blog' },
{ label: 'Now', path: 'now' },
{ label: 'Projects', path: 'projects' },
];
const pathComponents = pathname.split('/').slice(1);
---
<header>
<h1>ghall.blog</h1>
<div>
{
pathComponents[0] !== '' ? (
<a href="/" transition:name="my-avatar">
<Avatar size={60} />
</a>
) : null
}
</div>
<nav>
{
navLinks.map((link) => (
<li>
<a
href={link.path}
class={pathname === link.path ? 'selected' : null}
>
<a href={`/${link.path}`}>
<span>{link.label}</span>
</a>
{pathComponents[0] === link.path ? (
<div class="underline" transition:name="menu-selection-indicator" />
) : null}
</li>
))
}
</nav>
</header>
<style>
.underline {
height: 2px;
width: 100%;
background-color: var(--orange);
}
</style>
+49
View File
@@ -0,0 +1,49 @@
<div
x-data="{
latestPost: null,
fetchPost() {
fetch('https://mastodon.social/@ghalldev.rss')
.then((response) => response.text())
.then((str) =>
new window.DOMParser().parseFromString(str, 'text/xml')
)
.then((data) => {
const item = data.querySelector('item');
console.log(item)
this.latestPost = {
link: item.querySelector('link').textContent,
body: item.querySelector('description').textContent,
};
})
.catch((error) => {
console.error('Error fetching feed:', error);
});
},
}"
x-init="fetchPost()"
>
<template x-if="latestPost">
<div class="mastodon-post">
<h2>Latest Mastodon Post</h2>
<p x-html="latestPost.body"></p>
<a x-bind:href="latestPost.link" target="_blank">View Post</a>
</div>
</template>
<template x-if="!latestPost">
<p>Loading...</p>
</template>
</div>
<style>
.mastodon-post {
margin: 2rem auto;
padding: 1rem;
max-width: 600px;
border-radius: var(--radius);
border: var(--border);
}
.mastodon-post > h2 {
margin-top: 0;
}
</style>
+3 -5
View File
@@ -19,9 +19,7 @@ const { post } = Astro.props;
const { data, slug } = post;
---
<article>
<div>
<BlogHeader title={post.data.title} date={data.pubDate} slug={slug} />
<Tags tags={data.tags} />
</div>
<article transition:name={slug}>
<BlogHeader title={post.data.title} date={data.pubDate} slug={slug} />
<Tags tags={data.tags} />
</article>
+1 -1
View File
@@ -9,7 +9,7 @@ const { tags } = Astro.props;
<span>
{
tags.sort().map((tag: string, index: number) => (
<a class="tag" href={`/archive/${tag}`}>
<a class="tag" href={`/blog/archive/${tag}`}>
{`${tag}${index < tags.length - 1 ? ', ' : ''}`}
</a>
))
+3
View File
@@ -1,2 +1,5 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
interface Window {
Alpine: import('alpinejs').Alpine;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M5 12l6 6" /><path d="M5 12l6 -6" /></svg>

After

Width:  |  Height:  |  Size: 386 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -3.268 64 68.414" width="2232" height="2500"><path fill="#0085ff" d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805zm36.254 0C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745z"/></svg>

After

Width:  |  Height:  |  Size: 619 B

Before

Width:  |  Height:  |  Size: 630 B

After

Width:  |  Height:  |  Size: 630 B

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="currentColor" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"><path d="M21.327 8.566c0-4.339-2.843-5.61-2.843-5.61-1.433-.658-3.894-.935-6.451-.956h-.063c-2.557.021-5.016.298-6.45.956 0 0-2.843 1.272-2.843 5.61 0 .993-.019 2.181.012 3.441.103 4.243.778 8.425 4.701 9.463 1.809.479 3.362.579 4.612.51 2.268-.126 3.541-.809 3.541-.809l-.075-1.646s-1.621.511-3.441.449c-1.804-.062-3.707-.194-3.999-2.409a4.523 4.523 0 0 1-.04-.621s1.77.433 4.014.536c1.372.063 2.658-.08 3.965-.236 2.506-.299 4.688-1.843 4.962-3.254.434-2.223.398-5.424.398-5.424zm-3.353 5.59h-2.081V9.057c0-1.075-.452-1.62-1.357-1.62-1 0-1.501.647-1.501 1.927v2.791h-2.069V9.364c0-1.28-.501-1.927-1.502-1.927-.905 0-1.357.546-1.357 1.62v5.099H6.026V8.903c0-1.074.273-1.927.823-2.558.566-.631 1.307-.955 2.228-.955 1.065 0 1.872.409 2.405 1.228l.518.869.519-.869c.533-.819 1.34-1.228 2.405-1.228.92 0 1.662.324 2.228.955.549.631.822 1.484.822 2.558v5.253z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 439 B

+5 -7
View File
@@ -16,7 +16,7 @@ const title = Astro.props.title || Astro.props.frontmatter?.title || 'Unknown';
---
<!doctype html>
<html lang="en" transition:animate="none">
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
@@ -29,16 +29,14 @@ const title = Astro.props.title || Astro.props.frontmatter?.title || 'Unknown';
type="application/rss+xml"
title="ghall.blog - RSS"
href={`${Astro.site}rss.xml`}
/><title>{`ghall.blog - ${title}`}</title></head
>
<ClientRouter />
<body class="layout-simple">
/><title>{`ghall.blog - ${title}`}</title>
<ClientRouter />
</head>
<body class="layout-simple" transition:animate="fade">
<Header />
<main>
<slot />
</main>
<Footer />
</body>
</html>
+28
View File
@@ -0,0 +1,28 @@
---
import Layout from '@layouts/Layout.astro';
import { Image } from 'astro:assets';
import DataGif from '../img/it-does-not-exist.gif';
---
<Layout title="Not Found">
<h2>404 - Page Not Found</h2>
<Image
class="gif"
src={DataGif}
alt="Data from Star Trek with the caption 'It does not exist'"
/>
</Layout>
<style>
h2 {
text-align: center;
}
.gif {
display: block;
margin: auto;
border-radius: var(--radius);
box-shadow: var(--shadow);
}
</style>
-10
View File
@@ -1,10 +0,0 @@
---
layout: ../layouts/Layout.astro
title: Page Not Found
---
## 404 - Page not found!
You're trying to find a page that does not exist.
[Click here](/) to find your way home.
-48
View File
@@ -1,48 +0,0 @@
---
export const prerender = true;
import Layout from '@layouts/Layout.astro';
---
<Layout title="About">
<img src="/portrait.jpg" alt="me" class="portrait" />
<p>My name is <strong>Graham</strong>, a full-stack web developer.</p>
<p>
When I'm not writing code, I'm usually enjoying one of my other hobbies;
video games, board games, music, hiking, photography, art, the list goes
on...
</p>
<p>
I also like writing about stuff, so I put together this site to share
whatever's on my mind about the world of tech, gaming, life, web
development, and whatever else strikes my fancy.
</p>
<p>
If your interested in checking out my web dev projects, check out <a
href="https://ghall.dev/"
target="_blank">my portfolio</a
>.
</p>
<p>
If you want to get in touch, I'm on <a
rel="me"
href="https://mastodon.social/@ghalldev"
target="_blank">Mastodon</a
>.
</p>
<p>
My portrait was drawn by <a
href="https://www.nataliavazquezgarcia.com"
target="_blank">Natalia Vazquez</a
>.
</p>
</Layout>
<style>
.portrait {
display: block;
max-width: 250px;
margin: auto;
margin-bottom: 26px;
border-radius: 200px;
}
</style>
@@ -15,18 +15,18 @@ export async function getStaticPaths() {
const { post } = Astro.props;
const { data } = post;
const { data, slug } = post;
const { Content } = await post.render();
---
<Layout title={data.title}>
<BlogHeader title={data.title} date={data.pubDate} />
<article>
<article transition:name={slug}>
<BlogHeader title={data.title} date={data.pubDate} />
<Content />
<a href="https://notbyai.fyi/">
<i class="not-by-ai"></i>
</a>
<Tags tags={data.tags} />
</article>
<a href="https://notbyai.fyi/">
<i class="not-by-ai"></i>
</a>
<Tags tags={data.tags} />
</Layout>
@@ -50,7 +50,7 @@ posts.sort(
<ul>
{
posts.map(({ slug, data }) => (
<li>
<li transition:name={slug}>
<a href={`/posts/${slug}`}>{data.title}</a> -
<span>
{format(add(new Date(data.pubDate), { hours: 6 }), 'MMM do, y')}
@@ -60,3 +60,9 @@ posts.sort(
}
</ul>
</Layout>
<style>
ul {
list-style: none;
}
</style>
+28
View File
@@ -0,0 +1,28 @@
---
import { getCollection } from 'astro:content';
import Layout from '@layouts/Layout.astro';
import PostPreview from '@components/PostPreview.astro';
const posts = await getCollection('blog');
---
<Layout title="Blog">
{
posts
.sort(
(a, b) =>
new Date(b.data.pubDate).valueOf() -
new Date(a.data.pubDate).valueOf()
)
.slice(0, 10)
.map((post) => <PostPreview post={post} />)
}
{
posts.length < 6 ? null : (
<div class="more-posts">
<a href="blog/archive/all">All Posts</a>
</div>
)
}
</Layout>
+64 -19
View File
@@ -1,28 +1,73 @@
---
export const prerender = true;
import { Image } from 'astro:assets';
import { getCollection } from 'astro:content';
import Layout from '@layouts/Layout.astro';
import PostPreview from '@components/PostPreview.astro';
import Avatar from '@components/Avatar.astro';
import portrait from '../img/portrait.jpg';
import MastodonIcon from '../img/svg/mastodon.svg';
import BlueskyIcon from '../img/svg/bluesky.svg';
const iconSize = 16;
const posts = await getCollection('blog');
const latestPost = posts.sort(
(a, b) =>
new Date(b.data.pubDate).valueOf() - new Date(a.data.pubDate).valueOf()
)[0];
---
<Layout title="Home">
{
posts
.sort(
(a, b) =>
new Date(b.data.pubDate).valueOf() -
new Date(a.data.pubDate).valueOf()
)
.slice(0, 10)
.map((post) => <PostPreview post={post} />)
}
{
posts.length < 6 ? null : (
<div class="more-posts">
<a href="/archive/all">All Posts</a>
</div>
)
}
<Layout title="Welcome">
<div transition:name="my-avatar">
<Avatar size={200} />
</div>
<p>
My name is <strong>Graham</strong> (he/him), a full-stack web developer, and
tech enthusiast.
</p>
<p>
When I'm not writing code, I'm usually enjoying one of my other hobbies;
video games, board games, music, hiking, photography, art, the list goes
on...
</p>
<p>
I also like writing about stuff, so I put together this site to share
whatever's on my mind about the world of tech, gaming, life, web
development, and whatever else strikes my fancy.
</p>
<p>
Read my latest blog post, <a href={`blog/${latestPost.slug}`}
>{latestPost.data.title}</a
>, from {new Date(latestPost.data.pubDate).toLocaleDateString()}.
</p>
<p>
If your interested in checking out my web dev projects, check out <a
href="https://ghall.dev/"
target="_blank">my portfolio</a
>.
</p>
<p>
If you want to get in touch, I'm on <a
rel="me"
href="https://mastodon.social/@ghalldev"
target="_blank"><MastodonIcon size={iconSize} />Mastodon</a
> and <a
href="https://bsky.app/profile/ghalldev.bsky.social"
target="_blank"><BlueskyIcon size={iconSize} />Bluesky</a
>.
</p>
<p>
My portrait was drawn by <a
href="https://www.nataliavazquezgarcia.com"
target="_blank">Natalia Vazquez</a
>.
</p>
</Layout>
<style>
a > svg {
transform: translateY(0.18rem);
}
</style>
-6
View File
@@ -7,12 +7,6 @@ Hey there, this is my [/now page](https://nownownow.com/about)!
_Last updated: February 4, 2025_
## 🔨 Making
- This blog!
- [AutoDock](https://github.com/ghall89/AutoDock) - A Mac menubar utility that automatically hides and shows your dock based on the size of your connected display.
- [KeyStash](https://github.com/ghall89/KeyStash) - A native Mac app for managing registration codes for your apps, modelled on the feature from 1Password.
## 🎧 Listening
- [Tonight - Franz Ferdinand](https://music.apple.com/us/album/tonight/300683347)
+16
View File
@@ -0,0 +1,16 @@
---
layout: ../layouts/Layout.astro
title: Projects
---
### [AutoDock](https://github.com/ghall89/AutoDock)
A MacOS menubar utility for automatically hiding and showing the Dock based on the screen size of the connected displays.
### [KeyStash](https://github.com/ghall89/KeyStash)
A MacOS application for managing software license keys for software purchased outside the Mac App Store.
### BGG Search (Raycast Extension) - Coming Soon!
A Raycast extension for searching [BoardGameGeek.com](https://boardgamegeek.com).
+1 -1
View File
@@ -16,7 +16,7 @@ export async function GET(context) {
.map((post) => ({
title: post.data.title,
pubDate: post.data.pubDate,
link: `/posts/${post.slug}`,
link: `/blog/${post.slug}`,
categories: post.data.tags,
content: parser.render(post.body),
})),
+8 -2
View File
@@ -70,6 +70,12 @@ header {
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
height: 120px;
}
header > h1 > a {
color: var(--text);
}
header > nav {
@@ -89,8 +95,8 @@ header > nav > li > .selected {
}
footer {
border-top: var(--border);
margin-top: 2rem;
margin: 4rem 0;
text-align: center;
}
article img {