✨ Mastodon post on homepage
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"$ref": "#/definitions/blog",
|
||||||
|
"definitions": {
|
||||||
|
"blog": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pubDate": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"pubDate",
|
||||||
|
"tags"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||||
|
}
|
||||||
Vendored
+218
@@ -0,0 +1,218 @@
|
|||||||
|
declare module 'astro:content' {
|
||||||
|
interface Render {
|
||||||
|
'.mdx': Promise<{
|
||||||
|
Content: import('astro').MarkdownInstance<{}>['Content'];
|
||||||
|
headings: import('astro').MarkdownHeading[];
|
||||||
|
remarkPluginFrontmatter: Record<string, any>;
|
||||||
|
components: import('astro').MDXInstance<{}>['components'];
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'astro:content' {
|
||||||
|
export interface RenderResult {
|
||||||
|
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
|
||||||
|
headings: import('astro').MarkdownHeading[];
|
||||||
|
remarkPluginFrontmatter: Record<string, any>;
|
||||||
|
}
|
||||||
|
interface Render {
|
||||||
|
'.md': Promise<RenderResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RenderedContent {
|
||||||
|
html: string;
|
||||||
|
metadata?: {
|
||||||
|
imagePaths: Array<string>;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'astro:content' {
|
||||||
|
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
|
||||||
|
|
||||||
|
export type CollectionKey = keyof AnyEntryMap;
|
||||||
|
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
|
||||||
|
|
||||||
|
export type ContentCollectionKey = keyof ContentEntryMap;
|
||||||
|
export type DataCollectionKey = keyof DataEntryMap;
|
||||||
|
|
||||||
|
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
|
||||||
|
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
|
||||||
|
ContentEntryMap[C]
|
||||||
|
>['slug'];
|
||||||
|
|
||||||
|
export type ReferenceDataEntry<
|
||||||
|
C extends CollectionKey,
|
||||||
|
E extends keyof DataEntryMap[C] = string,
|
||||||
|
> = {
|
||||||
|
collection: C;
|
||||||
|
id: E;
|
||||||
|
};
|
||||||
|
export type ReferenceContentEntry<
|
||||||
|
C extends keyof ContentEntryMap,
|
||||||
|
E extends ValidContentEntrySlug<C> | (string & {}) = string,
|
||||||
|
> = {
|
||||||
|
collection: C;
|
||||||
|
slug: E;
|
||||||
|
};
|
||||||
|
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
|
||||||
|
collection: C;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @deprecated Use `getEntry` instead. */
|
||||||
|
export function getEntryBySlug<
|
||||||
|
C extends keyof ContentEntryMap,
|
||||||
|
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||||
|
>(
|
||||||
|
collection: C,
|
||||||
|
// Note that this has to accept a regular string too, for SSR
|
||||||
|
entrySlug: E,
|
||||||
|
): E extends ValidContentEntrySlug<C>
|
||||||
|
? Promise<CollectionEntry<C>>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
|
||||||
|
/** @deprecated Use `getEntry` instead. */
|
||||||
|
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
|
||||||
|
collection: C,
|
||||||
|
entryId: E,
|
||||||
|
): Promise<CollectionEntry<C>>;
|
||||||
|
|
||||||
|
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
|
||||||
|
collection: C,
|
||||||
|
filter?: (entry: CollectionEntry<C>) => entry is E,
|
||||||
|
): Promise<E[]>;
|
||||||
|
export function getCollection<C extends keyof AnyEntryMap>(
|
||||||
|
collection: C,
|
||||||
|
filter?: (entry: CollectionEntry<C>) => unknown,
|
||||||
|
): Promise<CollectionEntry<C>[]>;
|
||||||
|
|
||||||
|
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
|
||||||
|
collection: C,
|
||||||
|
filter?: LiveLoaderCollectionFilterType<C>,
|
||||||
|
): Promise<
|
||||||
|
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof ContentEntryMap,
|
||||||
|
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||||
|
>(
|
||||||
|
entry: ReferenceContentEntry<C, E>,
|
||||||
|
): E extends ValidContentEntrySlug<C>
|
||||||
|
? Promise<CollectionEntry<C>>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof DataEntryMap,
|
||||||
|
E extends keyof DataEntryMap[C] | (string & {}),
|
||||||
|
>(
|
||||||
|
entry: ReferenceDataEntry<C, E>,
|
||||||
|
): E extends keyof DataEntryMap[C]
|
||||||
|
? Promise<DataEntryMap[C][E]>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof ContentEntryMap,
|
||||||
|
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||||
|
>(
|
||||||
|
collection: C,
|
||||||
|
slug: E,
|
||||||
|
): E extends ValidContentEntrySlug<C>
|
||||||
|
? Promise<CollectionEntry<C>>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof DataEntryMap,
|
||||||
|
E extends keyof DataEntryMap[C] | (string & {}),
|
||||||
|
>(
|
||||||
|
collection: C,
|
||||||
|
id: E,
|
||||||
|
): E extends keyof DataEntryMap[C]
|
||||||
|
? string extends keyof DataEntryMap[C]
|
||||||
|
? Promise<DataEntryMap[C][E]> | undefined
|
||||||
|
: Promise<DataEntryMap[C][E]>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
|
||||||
|
collection: C,
|
||||||
|
filter: string | LiveLoaderEntryFilterType<C>,
|
||||||
|
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
|
||||||
|
|
||||||
|
/** Resolve an array of entry references from the same collection */
|
||||||
|
export function getEntries<C extends keyof ContentEntryMap>(
|
||||||
|
entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
|
||||||
|
): Promise<CollectionEntry<C>[]>;
|
||||||
|
export function getEntries<C extends keyof DataEntryMap>(
|
||||||
|
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
|
||||||
|
): Promise<CollectionEntry<C>[]>;
|
||||||
|
|
||||||
|
export function render<C extends keyof AnyEntryMap>(
|
||||||
|
entry: AnyEntryMap[C][string],
|
||||||
|
): Promise<RenderResult>;
|
||||||
|
|
||||||
|
export function reference<C extends keyof AnyEntryMap>(
|
||||||
|
collection: C,
|
||||||
|
): import('astro/zod').ZodEffects<
|
||||||
|
import('astro/zod').ZodString,
|
||||||
|
C extends keyof ContentEntryMap
|
||||||
|
? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
|
||||||
|
: ReferenceDataEntry<C, keyof DataEntryMap[C]>
|
||||||
|
>;
|
||||||
|
// Allow generic `string` to avoid excessive type errors in the config
|
||||||
|
// if `dev` is not running to update as you edit.
|
||||||
|
// Invalid collection names will be caught at build time.
|
||||||
|
export function reference<C extends string>(
|
||||||
|
collection: C,
|
||||||
|
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
|
||||||
|
|
||||||
|
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
|
||||||
|
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
|
||||||
|
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
|
||||||
|
>;
|
||||||
|
|
||||||
|
type ContentEntryMap = {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
type DataEntryMap = {
|
||||||
|
"blog": Record<string, {
|
||||||
|
id: string;
|
||||||
|
body?: string;
|
||||||
|
collection: "blog";
|
||||||
|
data: InferEntrySchema<"blog">;
|
||||||
|
rendered?: RenderedContent;
|
||||||
|
filePath?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
type AnyEntryMap = ContentEntryMap & DataEntryMap;
|
||||||
|
|
||||||
|
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
|
||||||
|
infer TData,
|
||||||
|
infer TEntryFilter,
|
||||||
|
infer TCollectionFilter,
|
||||||
|
infer TError
|
||||||
|
>
|
||||||
|
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
|
||||||
|
: { data: never; entryFilter: never; collectionFilter: never; error: never };
|
||||||
|
type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
|
||||||
|
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
|
||||||
|
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
|
||||||
|
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
|
||||||
|
|
||||||
|
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
|
||||||
|
LiveContentConfig['collections'][C]['schema'] extends undefined
|
||||||
|
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
|
||||||
|
: import('astro/zod').infer<
|
||||||
|
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
|
||||||
|
>;
|
||||||
|
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
|
||||||
|
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
|
||||||
|
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
|
||||||
|
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
|
||||||
|
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
|
||||||
|
LiveContentConfig['collections'][C]['loader']
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type ContentConfig = typeof import("../src/content.config.js");
|
||||||
|
export type LiveContentConfig = never;
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -1 +1,2 @@
|
|||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
|
/// <reference path="content.d.ts" />
|
||||||
@@ -9,6 +9,7 @@ import alpinejs from '@astrojs/alpinejs';
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: 'https://ghall.space',
|
site: 'https://ghall.space',
|
||||||
output: 'static',
|
output: 'static',
|
||||||
|
|
||||||
integrations: [mdx(), alpinejs()],
|
integrations: [mdx(), alpinejs()],
|
||||||
redirects: {
|
redirects: {
|
||||||
'/posts/[...slug]': '/blog/[...slug]',
|
'/posts/[...slug]': '/blog/[...slug]',
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ const { title, date, slug } = Astro.props;
|
|||||||
---
|
---
|
||||||
|
|
||||||
<div class="blog-header">
|
<div class="blog-header">
|
||||||
<h2>
|
{slug ? <a href={`/blog/${slug}`}>{title}</a> : <h1>{title}</h1>}
|
||||||
{slug ? <a href={`/blog/${slug}`}>{title}</a> : title}
|
|
||||||
</h2>
|
|
||||||
<div>
|
<div>
|
||||||
<CalendarIcon class="calendar-icon" width={24} height={24} />
|
<CalendarIcon class="calendar-icon" width={24} height={24} />
|
||||||
<strong>{format(add(new Date(date), { hours: 6 }), 'MMM do, y')}</strong>
|
<strong>{format(add(new Date(date), { hours: 6 }), 'MMM do, y')}</strong>
|
||||||
@@ -23,11 +21,15 @@ const { title, date, slug } = Astro.props;
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
a {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
.calendar-icon {
|
.calendar-icon {
|
||||||
transform: translateY(0.3rem);
|
transform: translateY(0.3rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
font-weight: bold;
|
font-weight: bolder;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Drawer from './Drawer.astro';
|
|||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1><a href="/">ghall.space</a></h1>
|
<a href="/">ghall.space</a>
|
||||||
<Nav />
|
<Nav />
|
||||||
<Drawer />
|
<Drawer />
|
||||||
</div>
|
</div>
|
||||||
@@ -21,6 +21,7 @@ import Drawer from './Drawer.astro';
|
|||||||
top: 0;
|
top: 0;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
border-bottom: var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@@ -34,8 +35,15 @@ import Drawer from './Drawer.astro';
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 > a {
|
a {
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
header {
|
||||||
|
background-color: rgba(30, 30, 46, 90%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const pathComponents = pathname.split('/').slice(1);
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
@@ -33,7 +34,6 @@ const pathComponents = pathname.split('/').slice(1);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.underline {
|
.underline {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
this.latestPost = {
|
this.latestPost = {
|
||||||
link: item.querySelector('link').textContent,
|
link: item.querySelector('link').textContent,
|
||||||
body: item.querySelector('description').textContent,
|
body: item.querySelector('description').textContent,
|
||||||
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { z, defineCollection } from 'astro:content';
|
import { z, defineCollection } from 'astro:content';
|
||||||
|
import { glob } from 'astro/loaders';
|
||||||
|
|
||||||
const blogCollection = defineCollection({
|
const blogCollection = defineCollection({
|
||||||
|
loader: glob({ pattern: '**\/*.md', base: './src/content/blog' }),
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
pubDate: z.string().transform((str: string) => new Date(str)),
|
pubDate: z.string().transform((str: string) => new Date(str)),
|
||||||
+2
-2
@@ -19,7 +19,7 @@
|
|||||||
--background: #fcfcfc;
|
--background: #fcfcfc;
|
||||||
--radius: 5px;
|
--radius: 5px;
|
||||||
--shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
|
--shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
|
||||||
--border: 1px solid #8c8fa1;
|
--border: 1px solid #ced3f1;
|
||||||
--max-page-width: 800px;
|
--max-page-width: 800px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
--orange: #fab387;
|
--orange: #fab387;
|
||||||
--text: #cdd6f4;
|
--text: #cdd6f4;
|
||||||
--background: #1e1e2e;
|
--background: #1e1e2e;
|
||||||
--border: 1px solid rgb(131, 139, 167);
|
--border: 1px solid #505160;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { getCollection } from 'astro:content';
|
|||||||
|
|
||||||
import Layout from '@layouts/Layout.astro';
|
import Layout from '@layouts/Layout.astro';
|
||||||
import Avatar from '@components/Avatar.astro';
|
import Avatar from '@components/Avatar.astro';
|
||||||
|
import LatestPost from '@components/LatestPost.astro';
|
||||||
|
|
||||||
import MastodonIcon from '../assets/svg/mastodon.svg';
|
import MastodonIcon from '../assets/svg/mastodon.svg';
|
||||||
import BlueskyIcon from '../assets/svg/bluesky.svg';
|
import BlueskyIcon from '../assets/svg/bluesky.svg';
|
||||||
@@ -19,6 +20,7 @@ const latestPost = posts.sort(
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Welcome">
|
<Layout title="Welcome">
|
||||||
|
<Avatar size={200} />
|
||||||
<p>
|
<p>
|
||||||
My name is <strong>Graham</strong> (he/him), a full-stack web developer, and
|
My name is <strong>Graham</strong> (he/him), a full-stack web developer, and
|
||||||
tech enthusiast.
|
tech enthusiast.
|
||||||
@@ -61,10 +63,11 @@ const latestPost = posts.sort(
|
|||||||
target="_blank">Natalia Vazquez</a
|
target="_blank">Natalia Vazquez</a
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
|
<LatestPost />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
a > svg {
|
a > svg {
|
||||||
transform: translateY(0.18rem);
|
transform: translateY(0.1rem);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user