Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
651fe07
feat: barebones implementation of Blog Posts UI (WIP)
Kai-ros Jan 28, 2026
c5fcd2b
feat: generate blog posts content via markdown
jonathanyeong Jan 29, 2026
0878f16
feat: generate lexicons on postinstall & log out marshalling to stand…
jonathanyeong Jan 29, 2026
9d077fd
fix: clean up comments
jonathanyeong Jan 29, 2026
81868e0
fix: fix linting in markdown files
jonathanyeong Jan 29, 2026
1b253b2
feat: implemented BlogPostListCard component for the list page
Kai-ros Jan 30, 2026
6ec8568
feat: add unocss typography plugin for prose styling
jonathanyeong Jan 30, 2026
79e933e
fix: remove uneeded h1 style
jonathanyeong Jan 30, 2026
012472e
chore: Updated TODOs and added Docs page
Kai-ros Jan 30, 2026
c1b4ffc
fix: can't find lexicon types when running pnpm install
jonathanyeong Jan 30, 2026
6fc3a8f
fix: resolved styling issues
Kai-ros Jan 30, 2026
7e87ee1
feat: switch from nuxt/content to unplug-vue-markdown
jonathanyeong Jan 30, 2026
9d3714a
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 30, 2026
1f74b4b
feat: move blog link from app header to footer
jonathanyeong Jan 30, 2026
030df0f
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 30, 2026
a4039c2
docs: update todos
jonathanyeong Jan 30, 2026
7835ef2
Merge branch 'main' into feat/atproto-blog-fe
jonathanyeong Jan 31, 2026
127ad59
refactor: reworked nuxt module with validation, dedupe, and more
Kai-ros Jan 31, 2026
9f6bc48
fix: removed comment and extra line
Kai-ros Jan 31, 2026
ed96391
fix: add h1 to blog list page
abbeyperini Jan 31, 2026
6774771
feat: add hardcoded bluesky comment view
jonathanyeong Jan 31, 2026
391c883
feat: use constellation to fetch bluesky backlinks
jonathanyeong Jan 31, 2026
b76ba2a
fix: get backlinks working
jonathanyeong Feb 1, 2026
3ad742b
fix: add back in ttl and reverse order limiting to 1 record
jonathanyeong Feb 1, 2026
669063c
merge: resolve conflicts with main
danielroe Feb 1, 2026
b5b7830
fix: address rtl issues, clean up classes, and todos
jonathanyeong Feb 1, 2026
3dd0c3a
fix: remove keystroke in appHeader since we've moved blog link to footer
jonathanyeong Feb 1, 2026
8b22636
feat: add email templates for PDS triggered user notifications
Kai-ros Feb 1, 2026
07e440a
feat: added build and runtime comments refresh to bluesky comments and
Kai-ros Feb 2, 2026
ada797b
fix: added missing catch arg
Kai-ros Feb 2, 2026
a52e4b1
feat: added atomicity to the standard site sync
Kai-ros Feb 2, 2026
5c2ea8f
chore: remove templates extracted to PR
Kai-ros Feb 2, 2026
60825d6
chore: extract docs work into a separate PR -
Kai-ros Feb 2, 2026
4186d64
fix: fix issue with validateRecord not validating bsky posts
jonathanyeong Feb 3, 2026
ee45d2f
chore: clean up unused code and console logs
jonathanyeong Feb 3, 2026
6f3e8ec
feat: add support for authors in blog posts
jonathanyeong Feb 3, 2026
bf9009a
fix: added a slight background to the blog posts to increase readability
Kai-ros Feb 3, 2026
d7ae340
Merge branch 'main' into feat/atproto-blog-fe
jonathanyeong Feb 3, 2026
48f1479
fix: messed up merge conflict
jonathanyeong Feb 3, 2026
0f031d6
Merge branch 'main' into feat/atproto-blog-fe
jonathanyeong Feb 5, 2026
e802b5a
fix: update pnpm lockfile
jonathanyeong Feb 5, 2026
08161b9
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 5, 2026
1e3b943
chore: remove stale todo list
jonathanyeong Feb 5, 2026
ffe8e51
style: update blog list styling
jonathanyeong Feb 5, 2026
b239c75
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 6, 2026
3f600ef
style: add hover effect to blog post list card
jonathanyeong Feb 6, 2026
382a493
style: add arrow hover effect on blog post list card
jonathanyeong Feb 6, 2026
1d4f7d3
style: remove linear gradient and add date to blog post
jonathanyeong Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* background colors */
--bg: var(--bg-color, oklch(0.145 0 0));
--bg-subtle: var(--bg-subtle-color, oklch(0.178 0 0));
--bg-blog: oklch(0.195 0 0);
--bg-muted: var(--bg-muted-color, oklch(0.218 0 0));
--bg-elevated: var(--bg-elevated-color, oklch(0.252 0 0));

Expand Down
6 changes: 6 additions & 0 deletions app/components/AppFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ const isHome = computed(() => route.name === 'index')
<NuxtLink to="/about" class="link-subtle font-mono text-xs flex items-center">
{{ $t('footer.about') }}
</NuxtLink>
<NuxtLink
to="/blog"
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center"
>
{{ $t('footer.blog') }}
</NuxtLink>
<NuxtLink
to="/privacy"
class="link-subtle font-mono text-xs min-h-11 flex items-center gap-1 lowercase"
Expand Down
45 changes: 45 additions & 0 deletions app/components/AuthorAvatar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script setup lang="ts">
import type { ResolvedAuthor } from '#shared/schemas/blog'

const props = defineProps<{
author: ResolvedAuthor
size?: 'sm' | 'md' | 'lg'
}>()

const sizeClasses = computed(() => {
switch (props.size ?? 'md') {
case 'sm':
return 'w-8 h-8 text-sm'
case 'lg':
return 'w-12 h-12 text-xl'
default:
return 'w-10 h-10 text-lg'
}
})

const initials = computed(() =>
props.author.name
.split(' ')
.map(n => n[0])
.join('')
.toUpperCase()
.slice(0, 2),
)
</script>

<template>
<div
class="shrink-0 flex items-center justify-center border border-border rounded-full bg-bg-muted overflow-hidden"
:class="[sizeClasses]"
>
<img
v-if="author.avatar"
:src="author.avatar"
:alt="author.name"
class="w-full h-full object-cover"
/>
<span v-else class="text-fg-subtle font-mono" aria-hidden="true">
{{ initials }}
</span>
</div>
</template>
48 changes: 48 additions & 0 deletions app/components/AuthorList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script setup lang="ts">
import type { Author } from '#shared/schemas/blog'

const props = defineProps<{
authors: Author[]
variant?: 'compact' | 'expanded'
}>()

const { resolvedAuthors } = useAuthorProfiles(props.authors)
</script>

<template>
<!-- Expanded variant: vertical list with larger avatars -->
<div v-if="variant === 'expanded'" class="flex flex-wrap items-center gap-4">
<div v-for="author in resolvedAuthors" :key="author.name" class="flex items-center gap-2">
<AuthorAvatar :author="author" size="md" disable-link />
<div class="flex flex-col">
<span class="text-sm font-medium text-fg">{{ author.name }}</span>
<a
v-if="author.blueskyHandle && author.profileUrl"
:href="author.profileUrl"
target="_blank"
rel="noopener noreferrer"
class="text-xs text-fg-muted hover:text-primary transition-colors"
>
@{{ author.blueskyHandle }}
</a>
</div>
</div>
</div>

<!-- Compact variant: no avatars -->
<div v-else class="flex items-center gap-2 min-w-0">
<div class="flex items-center">
<AuthorAvatar
v-for="(author, index) in resolvedAuthors"
:key="author.name"
:author="author"
size="md"
class="ring-2 ring-bg"
:class="index > 0 ? '-ms-3' : ''"
/>
</div>
<span class="text-xs text-fg-muted font-mono truncate">
{{ resolvedAuthors.map(a => a.name).join(', ') }}
</span>
</div>
</template>
59 changes: 59 additions & 0 deletions app/components/BlogPostListCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script setup lang="ts">
import type { Author } from '#shared/schemas/blog'

defineProps<{
/** Authors of the blog post */
authors: Author[]
/** Blog Title */
title: string
/** Tags such as OpenSource, Architecture, Community, etc. */
topics: string[]
/** Brief line from the text. */
excerpt: string
/** The datetime value (ISO string or Date) */
published: string
/** Path/Slug of the post */
path: string
/** For keyboard nav scaffold */
index: number
}>()

const emit = defineEmits<{
focus: [index: number]
}>()
</script>

<template>
<article
class="group relative hover:bg-bg-subtle transition-colors duration-150 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50 -mx-4 px-4 -my-2 py-2 sm:-mx-6 sm:px-6 sm:-my-3 sm:py-3 sm:rounded-md"
>
<NuxtLink
:to="`/blog/${path}`"
:data-suggestion-index="index"
class="flex items-center gap-4 focus-visible:outline-none after:content-[''] after:absolute after:inset-0"
@focus="index != null && emit('focus', index)"
@mouseenter="index != null && emit('focus', index)"
>
<!-- Text Content -->
<div class="flex-1 min-w-0 text-left gap-2">
<span class="text-xs text-fg-muted font-mono">{{ published }}</span>
<h2
class="font-mono text-xl font-medium text-fg group-hover:text-primary transition-colors hover:underline"
>
{{ title }}
</h2>
<p v-if="excerpt" class="text-fg-muted leading-relaxed line-clamp-2 no-underline">
{{ excerpt }}
</p>
<div class="flex flex-wrap items-center gap-2 text-xs text-fg-muted font-mono mt-4">
<AuthorList :authors="authors" />
</div>
</div>

<span
class="i-carbon:arrow-right w-4 h-4 text-fg-subtle group-hover:text-fg relative inset-is-0 group-hover:inset-is-1 transition-all duration-200 shrink-0"
aria-hidden="true"
/>
</NuxtLink>
</article>
</template>
49 changes: 49 additions & 0 deletions app/components/BlogPostWrapper.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script setup lang="ts">
import type { BlogPostFrontmatter } from '#shared/schemas/blog'

const props = defineProps<{
frontmatter: BlogPostFrontmatter
}>()

useSeoMeta({
title: props.frontmatter.title,
description: props.frontmatter.description || props.frontmatter.excerpt,
ogTitle: props.frontmatter.title,
ogDescription: props.frontmatter.description || props.frontmatter.excerpt,
ogType: 'article',
})

const slug = computed(() => props.frontmatter.slug)

// Use Constellation to find the Bluesky post linking to this blog post
const { data: blueskyLink } = await useBlogPostBlueskyLink(slug)
const blueskyPostUri = computed(() => blueskyLink.value?.postUri ?? null)
</script>

<template>
<main class="container w-full py-8">
<article class="max-w-prose mx-auto p-2 border-b border-border">
<div class="text-sm text-fg-muted font-mono mb-4">
<DateTime :datetime="frontmatter.date" year="numeric" month="short" day="numeric" />
</div>
<slot />
</article>
<article v-if="frontmatter.authors" class="mt-12 max-w-prose mx-auto">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<AuthorList :authors="frontmatter.authors" variant="expanded" />
</div>
</article>

<!--
- Only renders if Constellation found a Bluesky post linking to this slug
- Cached API route avoids rate limits during build
-->
<LazyBlueskyComments v-if="blueskyPostUri" :post-uri="blueskyPostUri" />
</main>
</template>

<style scoped>
:deep(.markdown-body) {
@apply prose dark:prose-invert;
}
</style>
Loading
Loading