From 77a80c1567f17195587bd84f8d7abcfbe4307c06 Mon Sep 17 00:00:00 2001 From: Ewan Croft Date: Tue, 22 Jul 2025 10:27:44 +0100 Subject: [PATCH] feat(homepage): add latest blog post section and utility icons add LatestBlogPost component to display recent posts on homepage create and export new utility icons for better UI consistency refactor ArchiveCard to use new icon components update layout to fetch and display latest posts --- src/lib/components/archive/ArchiveCard.svelte | 46 ++----- src/lib/components/icons/index.ts | 10 +- .../icons/utility/ArrowRightUpIcon.svelte | 20 +++ .../components/icons/utility/BookIcon.svelte | 20 +++ .../components/icons/utility/BooksIcon.svelte | 20 +++ .../components/icons/utility/ClockIcon.svelte | 20 +++ .../icons/utility/CoffeeIcon.svelte | 23 ++++ .../icons/utility/DocumentIcon.svelte | 23 ++++ .../components/icons/utility/EditIcon.svelte | 21 +++ .../icons/utility/LinkExternalIcon.svelte | 20 +++ src/lib/components/layout/ThemeToggle.svelte | 21 +-- .../layout/main/LatestBlogPost.svelte | 123 ++++++++++++++++++ src/lib/components/layout/main/index.ts | 3 +- src/routes/+layout.ts | 92 +++++++++++-- src/routes/+page.svelte | 21 ++- 15 files changed, 416 insertions(+), 67 deletions(-) create mode 100644 src/lib/components/icons/utility/ArrowRightUpIcon.svelte create mode 100644 src/lib/components/icons/utility/BookIcon.svelte create mode 100644 src/lib/components/icons/utility/BooksIcon.svelte create mode 100644 src/lib/components/icons/utility/ClockIcon.svelte create mode 100644 src/lib/components/icons/utility/CoffeeIcon.svelte create mode 100644 src/lib/components/icons/utility/DocumentIcon.svelte create mode 100644 src/lib/components/icons/utility/EditIcon.svelte create mode 100644 src/lib/components/icons/utility/LinkExternalIcon.svelte create mode 100644 src/lib/components/layout/main/LatestBlogPost.svelte diff --git a/src/lib/components/archive/ArchiveCard.svelte b/src/lib/components/archive/ArchiveCard.svelte index 273fe51..ddbbc84 100644 --- a/src/lib/components/archive/ArchiveCard.svelte +++ b/src/lib/components/archive/ArchiveCard.svelte @@ -2,6 +2,12 @@ import { fly, fade } from "svelte/transition"; import { quintOut } from "svelte/easing"; import { formatDate } from "$lib/utils/dateFormatter"; + import DocumentIcon from "$lib/components/icons/utility/DocumentIcon.svelte"; + import LinkExternalIcon from "$lib/components/icons/utility/LinkExternalIcon.svelte"; + import CoffeeIcon from "$lib/components/icons/utility/CoffeeIcon.svelte"; + import ClockIcon from "$lib/components/icons/utility/ClockIcon.svelte"; + import BookIcon from "$lib/components/icons/utility/BookIcon.svelte"; + import BooksIcon from "$lib/components/icons/utility/BooksIcon.svelte"; export let type: 'post' | 'link'; export let post: any = {}; // For post type @@ -62,21 +68,12 @@
{#if type === 'post'}
- - - - - - - + Blog post
{:else} {/if} @@ -99,32 +96,13 @@
{#if getReadingTimeIcon(readingTime) === 'quick'} - - - - - - - - + {:else if getReadingTimeIcon(readingTime) === 'short'} - - - - - + {:else if getReadingTimeIcon(readingTime) === 'medium'} - - - - - + {:else} - - - - - + {/if}
{Math.ceil(post.wordCount / 200)} diff --git a/src/lib/components/icons/index.ts b/src/lib/components/icons/index.ts index 8195fc8..5fd6e29 100644 --- a/src/lib/components/icons/index.ts +++ b/src/lib/components/icons/index.ts @@ -10,4 +10,12 @@ export { default as HomeIcon } from './utility/HomeIcon.svelte'; export { default as LinkIcon } from './utility/LinkIcon.svelte'; export { default as MoonIcon } from './utility/MoonIcon.svelte'; export { default as PostIcon } from './utility/PostIcon.svelte'; -export { default as SunIcon } from './utility/SunIcon.svelte'; \ No newline at end of file +export { default as SunIcon } from './utility/SunIcon.svelte'; +export { default as DocumentIcon } from "./utility/DocumentIcon.svelte"; +export { default as LinkExternalIcon } from "./utility/LinkExternalIcon.svelte"; +export { default as CoffeeIcon } from "./utility/CoffeeIcon.svelte"; +export { default as ClockIcon } from "./utility/ClockIcon.svelte"; +export { default as BookIcon } from "./utility/BookIcon.svelte"; +export { default as BooksIcon } from "./utility/BooksIcon.svelte"; +export { default as EditIcon } from "./utility/EditIcon.svelte"; +export { default as ArrowRightUpIcon } from "./utility/ArrowRightUpIcon.svelte"; \ No newline at end of file diff --git a/src/lib/components/icons/utility/ArrowRightUpIcon.svelte b/src/lib/components/icons/utility/ArrowRightUpIcon.svelte new file mode 100644 index 0000000..8db6201 --- /dev/null +++ b/src/lib/components/icons/utility/ArrowRightUpIcon.svelte @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/src/lib/components/icons/utility/BookIcon.svelte b/src/lib/components/icons/utility/BookIcon.svelte new file mode 100644 index 0000000..628fcdb --- /dev/null +++ b/src/lib/components/icons/utility/BookIcon.svelte @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/src/lib/components/icons/utility/BooksIcon.svelte b/src/lib/components/icons/utility/BooksIcon.svelte new file mode 100644 index 0000000..ffb4cc4 --- /dev/null +++ b/src/lib/components/icons/utility/BooksIcon.svelte @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/src/lib/components/icons/utility/ClockIcon.svelte b/src/lib/components/icons/utility/ClockIcon.svelte new file mode 100644 index 0000000..1dba008 --- /dev/null +++ b/src/lib/components/icons/utility/ClockIcon.svelte @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/src/lib/components/icons/utility/CoffeeIcon.svelte b/src/lib/components/icons/utility/CoffeeIcon.svelte new file mode 100644 index 0000000..63d824f --- /dev/null +++ b/src/lib/components/icons/utility/CoffeeIcon.svelte @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/lib/components/icons/utility/DocumentIcon.svelte b/src/lib/components/icons/utility/DocumentIcon.svelte new file mode 100644 index 0000000..d6290e2 --- /dev/null +++ b/src/lib/components/icons/utility/DocumentIcon.svelte @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/lib/components/icons/utility/EditIcon.svelte b/src/lib/components/icons/utility/EditIcon.svelte new file mode 100644 index 0000000..8e4ab49 --- /dev/null +++ b/src/lib/components/icons/utility/EditIcon.svelte @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/src/lib/components/icons/utility/LinkExternalIcon.svelte b/src/lib/components/icons/utility/LinkExternalIcon.svelte new file mode 100644 index 0000000..73050cf --- /dev/null +++ b/src/lib/components/icons/utility/LinkExternalIcon.svelte @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/src/lib/components/layout/ThemeToggle.svelte b/src/lib/components/layout/ThemeToggle.svelte index cbacab3..34da16b 100644 --- a/src/lib/components/layout/ThemeToggle.svelte +++ b/src/lib/components/layout/ThemeToggle.svelte @@ -1,6 +1,7 @@ + +{#if latestPost} +
+
+

Latest Blog Post

+ + View All Posts + + +
+ +
+ +
+
+{/if} + + \ No newline at end of file diff --git a/src/lib/components/layout/main/index.ts b/src/lib/components/layout/main/index.ts index 64220e5..abc178b 100644 --- a/src/lib/components/layout/main/index.ts +++ b/src/lib/components/layout/main/index.ts @@ -1 +1,2 @@ -export { default as DynamicLinks } from "./DynamicLinks.svelte"; \ No newline at end of file +export { default as DynamicLinks } from "./DynamicLinks.svelte"; +export { default as LatestBlogPost } from "./LatestBlogPost.svelte"; \ No newline at end of file diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 929f0fe..16e7817 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -1,19 +1,17 @@ import { getProfile } from "$lib/components/profile/profile"; -import type { Profile, LinkBoard } from "$lib/components/shared"; +import type { Profile, LinkBoard, MarkdownPost, Post } from "$lib/components/shared"; +import { parse } from "$lib/parser"; // Profile data cache let profile: Profile; let dynamicLinks: LinkBoard | undefined; +let latestPosts: Post[] = []; export async function load({ fetch }) { if (profile === undefined) { profile = await getProfile(fetch); } - if (profile === undefined) { - profile = await getProfile(fetch); - } - // Fetch dynamic links only if not already cached if (dynamicLinks === undefined) { try { @@ -29,12 +27,86 @@ export async function load({ fetch }) { } } + // Fetch latest blog posts if not already cached + if (latestPosts.length === 0) { + try { + const rawResponse = await fetch( + `${profile.pds}/xrpc/com.atproto.repo.listRecords?repo=${profile.did}&collection=com.whtwnd.blog.entry` + ); + + if (rawResponse.ok) { + const response = await rawResponse.json(); + + if (response.records && response.records.length > 0) { + const mdposts: Map = new Map(); + + // Process only the latest few posts to keep it lightweight + const recentRecords = response.records + .filter((data: any) => { + const matches = data["uri"].split("/"); + const record = data["value"] || data.value; + return matches && matches.length === 5 && + record && + (record["visibility"] === "public" || !record["visibility"]); + }) + .slice(0, 3); // Only process the latest 3 posts for the homepage + + for (const data of recentRecords) { + const matches = data["uri"].split("/"); + const rkey = matches[matches.length - 1]; + const record = data["value"] || data.value; + + const content = record["content"] || record.content; + const title = record["title"] || record.title; + const createdAt = record["createdAt"] || record.createdAt; + + const actualContent = content || (record.value && record.value.content); + const actualTitle = title || (record.value && record.value.title); + const actualCreatedAt = createdAt || (record.value && record.value.createdAt); + + // Skip posts without content + if (!actualContent) continue; + + let createdAtDate: Date; + if (!actualCreatedAt) { + createdAtDate = new Date(); + } else { + createdAtDate = new Date(actualCreatedAt); + if (isNaN(createdAtDate.getTime())) { + continue; + } + } + + const finalTitle = actualTitle || `Untitled Post (${rkey})`; + + mdposts.set(rkey, { + title: finalTitle, + createdAt: createdAtDate, + mdcontent: actualContent, + rkey, + }); + } + + if (mdposts.size > 0) { + const parsedPosts = await parse(mdposts); + latestPosts = Array.from(parsedPosts.values()).sort( + (a, b) => b.createdAt.getTime() - a.createdAt.getTime() + ); + } + } + } + } catch (error) { + console.error("Error fetching latest blog posts:", error); + latestPosts = []; + } + } + return { profile, - pdsUrl: profile.pds, // Add pdsUrl from profile - did: profile.did, // Add did from profile - posts: new Map(), // Add empty posts map to match the expected type + pdsUrl: profile.pds, + did: profile.did, + posts: new Map(), // Keep this for compatibility with existing Footer component dynamicLinks, - + latestPosts, // Add latest posts for the homepage }; -} +} \ No newline at end of file diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 85b443c..cf38ede 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,9 +1,20 @@ @@ -41,5 +52,9 @@ - - + +{#if data.latestPosts && data.latestPosts.length > 0} + +{/if} + + \ No newline at end of file -- 2.51.2