Skip to content

Commit a5d25a3

Browse files
committed
First cut at blog impl; content collections working
1 parent 27733f2 commit a5d25a3

File tree

12 files changed

+483
-46
lines changed

12 files changed

+483
-46
lines changed
2.05 MB
Loading
155 KB
Loading
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
import '../../styles/global.css';
3+
import { Image } from 'astro:assets';
4+
import type { ImageMetadata } from 'astro';
5+
6+
const { author, date } = Astro.props;
7+
const avatarDir = '/src/assets/blog/avatars/';
8+
const avatarPath = avatarDir + author.data.avatar;
9+
const images = import.meta.glob<{ default: ImageMetadata }>('/src/assets/blog/avatars/*.{jpeg,jpg,png,gif}') // Must take a String literal otherwise we'd reuse avatarDir here
10+
if (!images[avatarPath]) throw new Error(`"${avatarPath}" does not exist`);
11+
---
12+
13+
<div class="flex border authorcard-card rounded-md">
14+
<div class="flex-none p-4"><Image src={images[avatarPath]()} alt={author.data.name} width={200} class="rounded-full border"/></div>
15+
<div class="flex-100 size-full">
16+
<div class="size-full">
17+
<h4>{ author.data.name }</h4>published on <i>{date}</i>
18+
<h6>{ author.data.title }</h6>
19+
</div>
20+
<div class="flex h-8">
21+
<div class="flex-none w-8 p-2 authorcard-killMargin"><a href={'https://x.com/' + author.data['social-x']} target="x">X</a></div>
22+
<div class="flex-none w-8 p-2 authorcard-killMargin"><a href={'https://github.com/' + author.data['social-github']} target="github">GitHub</a></div>
23+
<div class="flex-none w-8 p-2 authorcard-killMargin"><a href={'https://linkedin.com/in/' + author.data['social-linkedin']} target="linkedin">LinkedIn</a></div>
24+
<div class="flex-1"></div>
25+
</div>
26+
</div>
27+
</div>
28+
29+
<style>
30+
.authorcard-killMargin {
31+
margin-top: 0px !important;
32+
}
33+
.authorcard-card {
34+
border-color: var(--color-secondary) !important;
35+
}
36+
</style>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
import { formatDate } from './blogUtils.ts';
3+
4+
const { posts } = Astro.props;
5+
6+
// Helper to strip Markdown and get a plain text excerpt
7+
function getExcerpt(post: any) {
8+
if (post.data.snippet) return post.data.snippet;
9+
// Remove import statements and code blocks at the top
10+
let lines = post.body.split('\n');
11+
// Remove lines that are import statements or empty
12+
lines = lines.filter((line: string) =>
13+
!line.trim().startsWith('import') &&
14+
!line.trim().startsWith('export') &&
15+
!line.trim().startsWith('---') &&
16+
!line.trim().startsWith('```') &&
17+
!line.trim().startsWith('<') &&
18+
line.trim() !== ''
19+
);
20+
// Join remaining lines and remove Markdown formatting (basic)
21+
let text = lines.join(' ')
22+
.replace(/[#*_`>\-\[\]!]/g, '') // Remove Markdown symbols
23+
.replace(/<[^>]+>/g, '') // Remove HTML tags if any
24+
.trim();
25+
// Limit to 200 chars, end at word boundary
26+
if (text.length > 200) {
27+
text = text.slice(0, 200);
28+
const lastSpace = text.lastIndexOf(' ');
29+
if (lastSpace > 0) text = text.slice(0, lastSpace);
30+
text += '...';
31+
}
32+
return text;
33+
}
34+
---
35+
36+
<section>
37+
<section>
38+
{posts.map((post) => {
39+
return (
40+
<div
41+
class="rounded-xl shadow-md p-6 flex flex-col gap-2 border"
42+
style="background: var(--color-secondary); border-color: var(--color-secondary-foreground);"
43+
>
44+
<a
45+
href={import.meta.env.BASE_URL + "/blog/" + post.id}
46+
class="text-xl font-bold hover:underline transition-colors duration-150"
47+
style="color: var(--block-color-contrast-highest); font-family: var(--block-font-header);"
48+
onmouseover="this.style.color='var(--block-color-accent)';" onmouseout="this.style.color='var(--block-color-contrast-highest)';"
49+
>
50+
{post.data.title}
51+
</a>
52+
<div class="text-gray-500 text-xs mb-1">{formatDate(post.data.pubDate)}</div>
53+
<div class="text-gray-700 dark:text-gray-300 text-base mt-1"
54+
style="color: var(--block-color-contrast-high);">
55+
{getExcerpt(post)}
56+
</div>
57+
</div>
58+
)
59+
})}
60+
</section>
61+
</section>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export function formatDate(date:Date){
2+
const options = {
3+
year: 'numeric',
4+
month: 'long',
5+
day: 'numeric',
6+
weekday: 'long',
7+
};
8+
const formattedDate = date.toLocaleDateString('en-US', options);
9+
return formattedDate;
10+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import * as React from "react";
2+
import { cn } from "@/lib/utils";
3+
4+
const Alert = React.forwardRef<
5+
HTMLDivElement,
6+
React.HTMLAttributes<HTMLDivElement>
7+
>(({ className, ...props }, ref) => (
8+
<div
9+
ref={ref}
10+
role="alert"
11+
className={cn(
12+
"relative w-full rounded-lg border border-border bg-background px-4 py-3 text-sm [&:has(svg)]:pl-11",
13+
className
14+
)}
15+
{...props}
16+
/>
17+
));
18+
Alert.displayName = "Alert";
19+
20+
const AlertTitle = React.forwardRef<
21+
HTMLParagraphElement,
22+
React.HTMLAttributes<HTMLHeadingElement>
23+
>(({ className, ...props }, ref) => (
24+
<h5
25+
ref={ref}
26+
className={cn(
27+
"mb-1 font-medium leading-none tracking-tight",
28+
className
29+
)}
30+
{...props}
31+
/>
32+
));
33+
AlertTitle.displayName = "AlertTitle";
34+
35+
const AlertDescription = React.forwardRef<
36+
HTMLParagraphElement,
37+
React.HTMLAttributes<HTMLParagraphElement>
38+
>(({ className, ...props }, ref) => (
39+
<div
40+
ref={ref}
41+
className={cn("text-muted-foreground", className)}
42+
{...props}
43+
/>
44+
));
45+
AlertDescription.displayName = "AlertDescription";
46+
47+
export { Alert, AlertTitle, AlertDescription };
Lines changed: 55 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,64 @@
1-
"use client"
2-
31
import * as React from "react"
42
import * as TabsPrimitive from "@radix-ui/react-tabs"
53

64
import { cn } from "@/lib/utils"
75

8-
const Tabs = TabsPrimitive.Root
6+
function Tabs({
7+
className,
8+
...props
9+
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
10+
return (
11+
<TabsPrimitive.Root
12+
data-slot="tabs"
13+
className={cn("flex flex-col gap-2", className)}
14+
{...props}
15+
/>
16+
)
17+
}
918

10-
const TabsList = React.forwardRef<
11-
React.ElementRef<typeof TabsPrimitive.List>,
12-
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
13-
>(({ className, ...props }, ref) => (
14-
<TabsPrimitive.List
15-
ref={ref}
16-
className={cn(
17-
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
18-
className
19-
)}
20-
{...props}
21-
/>
22-
))
23-
TabsList.displayName = TabsPrimitive.List.displayName
19+
function TabsList({
20+
className,
21+
...props
22+
}: React.ComponentProps<typeof TabsPrimitive.List>) {
23+
return (
24+
<TabsPrimitive.List
25+
data-slot="tabs-list"
26+
className={cn(
27+
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
28+
className
29+
)}
30+
{...props}
31+
/>
32+
)
33+
}
2434

25-
const TabsTrigger = React.forwardRef<
26-
React.ElementRef<typeof TabsPrimitive.Trigger>,
27-
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
28-
>(({ className, ...props }, ref) => (
29-
<TabsPrimitive.Trigger
30-
ref={ref}
31-
className={cn(
32-
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
33-
className
34-
)}
35-
{...props}
36-
/>
37-
))
38-
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
35+
function TabsTrigger({
36+
className,
37+
...props
38+
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
39+
return (
40+
<TabsPrimitive.Trigger
41+
data-slot="tabs-trigger"
42+
className={cn(
43+
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
44+
className
45+
)}
46+
{...props}
47+
/>
48+
)
49+
}
3950

40-
const TabsContent = React.forwardRef<
41-
React.ElementRef<typeof TabsPrimitive.Content>,
42-
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
43-
>(({ className, ...props }, ref) => (
44-
<TabsPrimitive.Content
45-
ref={ref}
46-
className={cn(
47-
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
48-
className
49-
)}
50-
{...props}
51-
/>
52-
))
53-
TabsContent.displayName = TabsPrimitive.Content.displayName
51+
function TabsContent({
52+
className,
53+
...props
54+
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
55+
return (
56+
<TabsPrimitive.Content
57+
data-slot="tabs-content"
58+
className={cn("flex-1 outline-none", className)}
59+
{...props}
60+
/>
61+
)
62+
}
5463

55-
export { Tabs, TabsList, TabsTrigger, TabsContent }
64+
export { Tabs, TabsList, TabsTrigger, TabsContent }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"alrubinger" : {
3+
"name": "Andrew Lee Rubinger",
4+
"title": "Lead, Open Source Program Office, Block",
5+
"social-github": "ALRubinger",
6+
"social-linkedin": "alrubinger",
7+
"social-x": "ALRubinger",
8+
"avatar" : "alrubinger.jpg"
9+
},
10+
"angiejones" : {
11+
"name": "Angie Jones",
12+
"title": "Head of Developer Programs, Block",
13+
"social-github": "angiejones",
14+
"social-linkedin": "angiejones",
15+
"social-x": "angiejones",
16+
"avatar" : "angiejones.png"
17+
}
18+
}

0 commit comments

Comments
 (0)