initial commit
This commit is contained in:
commit
8a8f7c6f62
146 changed files with 8471 additions and 0 deletions
45
src/components/BaseHead.astro
Normal file
45
src/components/BaseHead.astro
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
// Import the global.css file here so that it is included on
|
||||
// all pages through the use of the <BaseHead /> component.
|
||||
import '../styles/global.css';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
|
||||
const { title, description, image = '/blog-placeholder-2.jpg' } = Astro.props;
|
||||
---
|
||||
|
||||
<!-- Global Metadata -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<link rel="stylesheet" href="/fonts/Iosevka.css">
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>{title}</title>
|
||||
<meta name="title" content={title} />
|
||||
<meta name="description" content={description} />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:image" content={new URL(image, Astro.url)} />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={Astro.url} />
|
||||
<meta property="twitter:title" content={title} />
|
||||
<meta property="twitter:description" content={description} />
|
||||
<meta property="twitter:image" content={new URL(image, Astro.url)} />
|
||||
48
src/components/Footer.astro
Normal file
48
src/components/Footer.astro
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
const today = new Date();
|
||||
---
|
||||
|
||||
<footer>
|
||||
© {today.getFullYear()} Zachary Myers. All rights reserved.
|
||||
<div class="social-links">
|
||||
<a href="https://twitter.com/_zackartz" target="_blank">
|
||||
<span class="sr-only">Follow Zack on Twitter</span>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/twitter"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
|
||||
></path></svg
|
||||
>
|
||||
</a>
|
||||
<a href="https://github.com/zackartz" target="_blank">
|
||||
<span class="sr-only">Go to Zacks's GitHub repo</span>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/github"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
|
||||
></path></svg
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
<style>
|
||||
footer {
|
||||
padding: 2em 1em 6em 1em;
|
||||
background: linear-gradient(var(--gray-gradient)) no-repeat;
|
||||
color: rgb(var(--gray));
|
||||
text-align: center;
|
||||
}
|
||||
.social-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.social-links a {
|
||||
text-decoration: none;
|
||||
color: rgb(var(--gray));
|
||||
}
|
||||
.social-links a:hover {
|
||||
color: rgb(var(--gray-dark));
|
||||
}
|
||||
</style>
|
||||
17
src/components/FormattedDate.astro
Normal file
17
src/components/FormattedDate.astro
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
interface Props {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
const { date } = Astro.props;
|
||||
---
|
||||
|
||||
<time datetime={date.toISOString()}>
|
||||
{
|
||||
date.toLocaleDateString('en-us', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})
|
||||
}
|
||||
</time>
|
||||
37
src/components/Header.astro
Normal file
37
src/components/Header.astro
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
import HeaderLink from './HeaderLink.astro';
|
||||
import { SITE_TITLE } from '../consts';
|
||||
---
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<h2 class="p-5 font-bold"><a href="/">{SITE_TITLE}</a></h2>
|
||||
<div class="absolute w-screen flex justify-center top-0 p-5 space-x-4">
|
||||
<HeaderLink href="/">Home</HeaderLink>
|
||||
<HeaderLink href="/blog">Blog</HeaderLink>
|
||||
<HeaderLink href="/about">About</HeaderLink>
|
||||
<HeaderLink target="_blank" href="/cv.pdf">CV</HeaderLink>
|
||||
</div>
|
||||
<div class="p-5 flex absolute top-0 right-0 space-x-4">
|
||||
<a href="https://twitter.com/_zackartz" target="_blank">
|
||||
<span class="sr-only">Follow Zack on Twitter</span>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" width="19" height="19" astro-icon="social/twitter"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
|
||||
></path></svg
|
||||
>
|
||||
</a>
|
||||
<a href="https://github.com/zackartz" target="_blank">
|
||||
<span class="sr-only">Go to Zacks's GitHub repo</span>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" width="19" height="19" astro-icon="social/github"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
|
||||
></path></svg
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
24
src/components/HeaderLink.astro
Normal file
24
src/components/HeaderLink.astro
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
import type { HTMLAttributes } from 'astro/types';
|
||||
|
||||
type Props = HTMLAttributes<'a'>;
|
||||
|
||||
const { href, class: className, ...props } = Astro.props;
|
||||
|
||||
const { pathname } = Astro.url;
|
||||
const subpath = pathname.match(/[^\/]+/g);
|
||||
const isActive = href === pathname || href === '/' + subpath?.[0];
|
||||
---
|
||||
|
||||
<a href={href} class:list={[className, { active: isActive }]} {...props}>
|
||||
<slot />
|
||||
</a>
|
||||
<style>
|
||||
a {
|
||||
@apply inline-block no-underline;
|
||||
}
|
||||
a.active {
|
||||
font-weight: bolder;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
5
src/consts.ts
Normal file
5
src/consts.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Place any global data in this file.
|
||||
// You can import this data from anywhere in your site by using the `import` keyword.
|
||||
|
||||
export const SITE_TITLE = "Zachary Myers";
|
||||
export const SITE_DESCRIPTION = "Software Engineer";
|
||||
131
src/content/blog/01.md
Normal file
131
src/content/blog/01.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# Deploying an Astro project on NixOS
|
||||
|
||||
[Nix](https://nixos.org) is an incredible project and has completely change the way I think about configuring linux and macOS environments. Recently, I moved my personal server from Ubuntu to NixOS to match my desktop environment. ([dotfiles here!](https://github.com/zackartz/nixos-dots)). In doing so, I realized I needed to move this blog over, too. I could simply deploy a docker container like I did before, but I think it would be interesting and informative to try and build a NixOS module around it. Hopefully you find it useful :)
|
||||
|
||||
## The `flake.nix` file.
|
||||
|
||||
Nix has a experimental feature called [flakes](https://nixos.wiki/wiki/Flakes), if you've been in the NixOS space long enough you've undoubtably heard of them. Flakes are a new way of writing your package configuration, you expose two objects, `inputs` and `outputs`. Your `inputs` would be things your application depends on, for example, `nixpkgs` (the package repository of Nix). Inputs can be a variety of different things, but typically are git repos. A `flake.lock` file is generated automatically so any consumers of your flake get the exact revisions of each input to guarantee reproducability. The `outputs` section of your flake can contain many things, from `devShells` to entire `nixosConfigurations`, but we are interested in `packages` and `nixosModules` today.
|
||||
|
||||
A example for a starter flake for a Astro project may look like this:
|
||||
|
||||
```nix
|
||||
{
|
||||
nputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
systems.url = "github:nix-systems/default";
|
||||
;
|
||||
|
||||
utputs = {
|
||||
systems,
|
||||
nixpkgs,
|
||||
...
|
||||
@ inputs: let
|
||||
eachSystem = f:
|
||||
nixpkgs.lib.genAttrs (import systems) (
|
||||
system:
|
||||
f nixpkgs.legacyPackages.${system}
|
||||
);
|
||||
n {
|
||||
devShells = eachSystem (pkgs: {
|
||||
default = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.nodejs
|
||||
|
||||
pkgs.nodePackages.pnpm
|
||||
|
||||
pkgs.nodePackages.typescript
|
||||
pkgs.nodePackages.typescript-language-server
|
||||
pkgs.nodePackages."@tailwindcss/language-server"
|
||||
pkgs.nodePackages."@astrojs/language-server"
|
||||
];
|
||||
};
|
||||
});
|
||||
;
|
||||
|
||||
```
|
||||
|
||||
You can see for my inputs I am taking in `nixpkgs` and `nix-systems`, which I am using to generate a devShell for every system architechture supported by NixOS. The `devShells` all import the following nix packages, `nodejs`, `pnpm`, `typescript`, `typescript-language-server`, `tailwindcss-language-server`, `astrojs-lanaguage-server`. Lets add add a package!
|
||||
|
||||
I use [pnpm](https://pnpm.io) as my package manager of choice, and as such we have to add the `pnpm2nix` flake to our inputs, which we can do with the following line.
|
||||
|
||||
```nix
|
||||
pnpm2nix.url = "github:nzbr/pnpm2nix-nzbr";
|
||||
```
|
||||
|
||||
Now, lets add the package spec:
|
||||
|
||||
```nix
|
||||
...
|
||||
|
||||
n {
|
||||
# add packages :)
|
||||
packages = eachSystem (pkgs: {
|
||||
default = inputs.pnpm2nix.packages.${pkgs.system}.mkPnpmPackage {
|
||||
name = "zm-blog";
|
||||
src = ./.;
|
||||
packageJSON = ./package.json;
|
||||
pnpmLock = ./pnpm-lock.yaml;
|
||||
};
|
||||
});
|
||||
|
||||
...
|
||||
;
|
||||
```
|
||||
|
||||
Now, when we run `nix build`, everything works as expected, great! But how can we see the outputs of our build? If we run the following command:
|
||||
|
||||
```bash
|
||||
| nix eval --raw .#packages.x86_64-linux.default
|
||||
/nix/store/ik5nmb60qrgib5knp4b538axwdxykc8z-zm-blog
|
||||
```
|
||||
|
||||
Awesome, if we ls this path, we get exactly what we are expecting from the build output.
|
||||
|
||||
```bash
|
||||
| ls /nix/store/ik5nmb60qrgib5knp4b538axwdxykc8z-zm-blog nix-shell-env
|
||||
_astro fonts blog-placeholder-2.jpg blog-placeholder-5.jpg rss.xml
|
||||
about index.html blog-placeholder-3.jpg blog-placeholder-about.jpg sitemap-0.xml
|
||||
blog blog-placeholder-1.jpg blog-placeholder-4.jpg favicon.svg sitemap-index.xml
|
||||
```
|
||||
|
||||
At this point, we could add this repo as a input to the flake configuring our server, add a new `virtualHost` for the domain we want this to run on, point the root at this package and call it a day, but I want to take it one step further. I want to write a NixOS module to make the configuration server side even easier.
|
||||
|
||||
Add the following code to the outputs section of your `flake.nix`.
|
||||
|
||||
```nix
|
||||
nixosModule = {
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.zmio.blog;
|
||||
in {
|
||||
options.zmio.blog = {
|
||||
enable = mkEnableOption "Enables the Blog Site";
|
||||
|
||||
domain = mkOption rec {
|
||||
type = type.str;
|
||||
default = "zackmyers.io";
|
||||
example = default;
|
||||
description = "The domain name for the website";
|
||||
};
|
||||
|
||||
ssl = mkOption rec {
|
||||
type = type.bool;
|
||||
default = true;
|
||||
example = default;
|
||||
description = "Whether to enable SSL on the domain or not";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.nginx.virtualHosts.${cfg.domain} = {
|
||||
forceSSL = cfg.ssl;
|
||||
enableACME = cfg.ssl;
|
||||
root = "${packages.${pkgs.system}.default}";
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
16
src/content/config.ts
Normal file
16
src/content/config.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { defineCollection, z } from "astro:content";
|
||||
|
||||
const blog = defineCollection({
|
||||
type: "content",
|
||||
// Type-check frontmatter using a schema
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
// Transform string to Date object
|
||||
pubDate: z.coerce.date(),
|
||||
updatedDate: z.coerce.date().optional(),
|
||||
heroImage: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { blog };
|
||||
2
src/env.d.ts
vendored
Normal file
2
src/env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
||||
84
src/layouts/BlogPost.astro
Normal file
84
src/layouts/BlogPost.astro
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
import BaseHead from '../components/BaseHead.astro';
|
||||
import Header from '../components/Header.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import FormattedDate from '../components/FormattedDate.astro';
|
||||
|
||||
type Props = any;
|
||||
|
||||
const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={title} description={description} />
|
||||
<style>
|
||||
main {
|
||||
width: calc(100% - 2em);
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.hero-image {
|
||||
width: 100%;
|
||||
}
|
||||
.hero-image img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
.prose {
|
||||
width: 720px;
|
||||
max-width: calc(100% - 2em);
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
color: rgb(var(--gray-dark));
|
||||
}
|
||||
.title {
|
||||
margin-bottom: 1em;
|
||||
padding: 1em 0;
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
}
|
||||
.title h1 {
|
||||
margin: 0 0 0.5em 0;
|
||||
}
|
||||
.date {
|
||||
margin-bottom: 0.5em;
|
||||
color: rgb(var(--gray));
|
||||
}
|
||||
.last-updated-on {
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Header />
|
||||
<main>
|
||||
<article>
|
||||
<div class="hero-image">
|
||||
{heroImage && <img width={1020} height={510} src={heroImage} alt="" />}
|
||||
</div>
|
||||
<div class="prose lg:prose-xl">
|
||||
<div class="title">
|
||||
<div class="date">
|
||||
<FormattedDate date={pubDate} />
|
||||
{
|
||||
updatedDate && (
|
||||
<div class="last-updated-on">
|
||||
Last updated on <FormattedDate date={updatedDate} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<h1>{title}</h1>
|
||||
<hr />
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
14
src/pages/about.astro
Normal file
14
src/pages/about.astro
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
import Layout from '../layouts/BlogPost.astro';
|
||||
---
|
||||
|
||||
<Layout
|
||||
title="About Me"
|
||||
description="About Zachary Myers"
|
||||
pubDate={new Date('April 13 2024')}
|
||||
>
|
||||
<article>
|
||||
<h3>Hey there!</h3>
|
||||
<p>My name is Zachary Myers, I am a software engineer from Honolulu, now living in Saint Joseph, MI. I am most interested in backend programming, and primarily use languages like Rust and Go, though also have plenty of experience with JavaScript and TypeScript as well. I am looking for work! Feel free to <a href="/cv.pdf" target="_blank">check out my cv</a> and <a href="mailto:me@zackmyers.io">reach out</a> if you are interested!</p>
|
||||
</article>
|
||||
</Layout>
|
||||
19
src/pages/blog/[...slug].astro
Normal file
19
src/pages/blog/[...slug].astro
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import BlogPost from '../../layouts/BlogPost.astro';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts: any[] = await getCollection('blog');
|
||||
return posts.map((post) => ({
|
||||
params: { slug: post.slug },
|
||||
props: post,
|
||||
}));
|
||||
}
|
||||
|
||||
const post: any = Astro.props;
|
||||
const { Content } = await post.render();
|
||||
---
|
||||
|
||||
<BlogPost class="prose lg:prose-xl" {...post.data}>
|
||||
<Content />
|
||||
</BlogPost>
|
||||
43
src/pages/blog/index.astro
Normal file
43
src/pages/blog/index.astro
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
import BaseHead from '../../components/BaseHead.astro';
|
||||
import Header from '../../components/Header.astro';
|
||||
import Footer from '../../components/Footer.astro';
|
||||
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
|
||||
import { getCollection } from 'astro:content';
|
||||
import FormattedDate from '../../components/FormattedDate.astro';
|
||||
|
||||
const posts: any[] = (await getCollection('blog')).sort(
|
||||
(a: any, b: any) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf()
|
||||
);
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
|
||||
</head>
|
||||
<body>
|
||||
<Header />
|
||||
<main class="w-full flex justify-center">
|
||||
{posts.length === 0 && <p class="py-48">Sorry! No posts at the minute, coming soon!</p>}
|
||||
<section>
|
||||
<ul class="space-y-4">
|
||||
{
|
||||
posts.sort((a, b) => b.data.pubDate - a.data.pubDate).map((post) => (
|
||||
<li class="p-4 rounded-xl shadow-lg border border-gray-300">
|
||||
<a href={`/blog/${post.slug}/`}>
|
||||
<img class="rounded-lg" width={720} height={360} src={post.data.heroImage} alt="" />
|
||||
<h4 class="text-2xl p-3 font-bold text-center">{post.data.title}</h4>
|
||||
<p class="text-gray-700 text-center">
|
||||
<FormattedDate date={post.data.pubDate} />
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
32
src/pages/index.astro
Normal file
32
src/pages/index.astro
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
import BaseHead from '../components/BaseHead.astro';
|
||||
import Header from '../components/Header.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
|
||||
</head>
|
||||
<body class="h-screen relative">
|
||||
<Header />
|
||||
<main class="p-16 flex flex-col w-full h-[84%]
|
||||
justify-center items-center">
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<h1 class="font-bold text-5xl">Zachary Myers</h1>
|
||||
<p class="py-4">Software Engineer, Hardware Hacker, Photographer</p>
|
||||
</div>
|
||||
<a href="/about">
|
||||
<button class="rounded-full bg-black text-white w-32 mt-4 flex px-4 py-3 justify-center">About me <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3" />
|
||||
</svg>
|
||||
</button>
|
||||
</a>
|
||||
<div class="absolute bottom-5">
|
||||
<Footer />
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
16
src/pages/rss.xml.js
Normal file
16
src/pages/rss.xml.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import rss from "@astrojs/rss";
|
||||
import { getCollection } from "astro:content";
|
||||
import { SITE_TITLE, SITE_DESCRIPTION } from "../consts";
|
||||
|
||||
export async function GET(context) {
|
||||
const posts = await getCollection("blog");
|
||||
return rss({
|
||||
title: SITE_TITLE,
|
||||
description: SITE_DESCRIPTION,
|
||||
site: context.site,
|
||||
items: posts.map((post) => ({
|
||||
...post.data,
|
||||
link: `/blog/${post.slug}/`,
|
||||
})),
|
||||
});
|
||||
}
|
||||
0
src/styles/global.css
Normal file
0
src/styles/global.css
Normal file
Loading…
Add table
Add a link
Reference in a new issue