update page structure
@@ -3,12 +3,17 @@ import { defineConfig } from 'astro/config';
|
|||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
import mdx from '@astrojs/mdx';
|
import mdx from '@astrojs/mdx';
|
||||||
|
|
||||||
|
import alpinejs from '@astrojs/alpinejs';
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: 'https://ghall.blog',
|
site: 'https://ghall.blog',
|
||||||
output: 'static',
|
output: 'static',
|
||||||
integrations: [mdx()],
|
integrations: [mdx(), alpinejs()],
|
||||||
experimental: {
|
experimental: {
|
||||||
svg: true,
|
svg: true,
|
||||||
},
|
},
|
||||||
|
redirects: {
|
||||||
|
'/posts/[...slug]': '/blog/[...slug]',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
@@ -12,9 +12,12 @@
|
|||||||
"lint": "eslint --ext .js --ext .ts --ext .astro"
|
"lint": "eslint --ext .js --ext .ts --ext .astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@astrojs/alpinejs": "^0.4.3",
|
||||||
"@astrojs/mdx": "4.0.2",
|
"@astrojs/mdx": "4.0.2",
|
||||||
"@astrojs/rss": "4.0.10",
|
"@astrojs/rss": "4.0.10",
|
||||||
|
"@types/alpinejs": "^3.13.11",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
|
"alpinejs": "^3.14.8",
|
||||||
"astro": "^5.1.8",
|
"astro": "^5.1.8",
|
||||||
"astro-pagefind": "^1.7.0",
|
"astro-pagefind": "^1.7.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { format, add } from 'date-fns';
|
import { format, add } from 'date-fns';
|
||||||
|
|
||||||
import CalendarIcon from '../styles/svg/calendar.svg';
|
import CalendarIcon from '../img/svg/calendar.svg';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: String;
|
title: String;
|
||||||
@@ -14,7 +14,7 @@ const { title, date, slug } = Astro.props;
|
|||||||
|
|
||||||
<div class="blog-header">
|
<div class="blog-header">
|
||||||
<h2>
|
<h2>
|
||||||
{slug ? <a href={`/posts/${slug}`}>{title}</a> : title}
|
{slug ? <a href={`/blog/${slug}`}>{title}</a> : title}
|
||||||
</h2>
|
</h2>
|
||||||
<div>
|
<div>
|
||||||
<CalendarIcon class="calendar-icon" size={24} />
|
<CalendarIcon class="calendar-icon" size={24} />
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
import RssIcon from '../styles/svg/rss.svg';
|
import RssIcon from '../img/svg/rss.svg';
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>
|
<p>
|
||||||
Copyright {year}, Graham Hall
|
Copyright 2022 - {year}, Graham Hall
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
Built with <a href="https://astro.build">Astro</a>
|
Built with <a href="https://astro.build">Astro</a>
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ interface NavLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const navLinks: NavLink[] = [
|
const navLinks: NavLink[] = [
|
||||||
{ label: 'Blog', icon: 'pen', path: '/' },
|
{ label: 'Home', icon: 'person', path: '' },
|
||||||
{ label: 'About', icon: 'person', path: '/about/' },
|
{ label: 'Blog', icon: 'pen', path: 'blog' },
|
||||||
{ label: 'Now', icon: 'clock', path: '/now/' },
|
{ label: 'Now', icon: 'clock', path: 'now' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const pathComponents = pathname.split('/').slice(1);
|
||||||
---
|
---
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
@@ -23,8 +25,8 @@ const navLinks: NavLink[] = [
|
|||||||
navLinks.map((link) => (
|
navLinks.map((link) => (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href={link.path}
|
href={`/${link.path}`}
|
||||||
class={pathname === link.path ? 'selected' : null}
|
class={pathComponents[0] === link.path ? 'selected' : null}
|
||||||
>
|
>
|
||||||
<span>{link.label}</span>
|
<span>{link.label}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -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');
|
||||||
|
this.latestPost = {
|
||||||
|
link: item.querySelector('link').textContent,
|
||||||
|
body: item.querySelector('description').textContent,
|
||||||
|
pubDate: item.querySelector('pubDate').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>
|
||||||
|
<small x-text="latestPost.pubDate"></small>
|
||||||
|
</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>
|
||||||
@@ -9,7 +9,7 @@ const { tags } = Astro.props;
|
|||||||
<span>
|
<span>
|
||||||
{
|
{
|
||||||
tags.sort().map((tag: string, index: number) => (
|
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 ? ', ' : ''}`}
|
{`${tag}${index < tags.length - 1 ? ', ' : ''}`}
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
/// <reference path="../.astro/types.d.ts" />
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
|
interface Window {
|
||||||
|
Alpine: import('alpinejs').Alpine;
|
||||||
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
@@ -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 |
@@ -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 |
@@ -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>
|
|
||||||
@@ -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="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="blog/archive/all">All Posts</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Layout>
|
||||||
@@ -1,28 +1,84 @@
|
|||||||
---
|
---
|
||||||
|
export const prerender = true;
|
||||||
|
import { Image } from 'astro:assets';
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
import Layout from '@layouts/Layout.astro';
|
import Layout from '@layouts/Layout.astro';
|
||||||
import PostPreview from '@components/PostPreview.astro';
|
import LatestPost from '@components/LatestPost.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 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">
|
<Layout title="About">
|
||||||
{
|
<Image
|
||||||
posts
|
src={portrait}
|
||||||
.sort(
|
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."
|
||||||
(a, b) =>
|
class="portrait"
|
||||||
new Date(b.data.pubDate).valueOf() -
|
width="250"
|
||||||
new Date(a.data.pubDate).valueOf()
|
height="250"
|
||||||
)
|
/>
|
||||||
.slice(0, 10)
|
<p>
|
||||||
.map((post) => <PostPreview post={post} />)
|
My name is <strong>Graham</strong> (he/him), a full-stack web developer, and
|
||||||
}
|
tech enthusiast.
|
||||||
{
|
</p>
|
||||||
posts.length < 6 ? null : (
|
<p>
|
||||||
<div class="more-posts">
|
When I'm not writing code, I'm usually enjoying one of my other hobbies;
|
||||||
<a href="/archive/all">All Posts</a>
|
video games, board games, music, hiking, photography, art, the list goes
|
||||||
</div>
|
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>
|
||||||
|
<LatestPost />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.portrait {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
margin-bottom: 26px;
|
||||||
|
border-radius: 200px;
|
||||||
|
}
|
||||||
|
a > svg {
|
||||||
|
transform: translateY(0.18rem);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export async function GET(context) {
|
|||||||
.map((post) => ({
|
.map((post) => ({
|
||||||
title: post.data.title,
|
title: post.data.title,
|
||||||
pubDate: post.data.pubDate,
|
pubDate: post.data.pubDate,
|
||||||
link: `/posts/${post.slug}`,
|
link: `/blog/${post.slug}`,
|
||||||
categories: post.data.tags,
|
categories: post.data.tags,
|
||||||
content: parser.render(post.body),
|
content: parser.render(post.body),
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ header {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > h1 > a {
|
||||||
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
header > nav {
|
header > nav {
|
||||||
@@ -89,8 +94,8 @@ header > nav > li > .selected {
|
|||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
border-top: var(--border);
|
margin: 4rem 0;
|
||||||
margin-top: 2rem;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
article img {
|
article img {
|
||||||
|
|||||||