mirror of
https://github.com/youwen5/site.git
synced 2024-11-24 17:33:51 -08:00
feat: crawl posts from blog directory
This commit is contained in:
parent
b8e9890a5f
commit
199d95680a
14 changed files with 108 additions and 98 deletions
|
@ -1,17 +1,16 @@
|
|||
title = "A Brief Introduction to Taylor Series"
|
||||
|
||||
[manifest]
|
||||
date = 2024-01-01T12:00:00Z
|
||||
date = 2024-04-12T20:48:40.799Z
|
||||
blurb = "The essence of calculus."
|
||||
description = "A brief introduction to Taylor series, a powerful tool in calculus. We will discuss the intuition behind Taylor series, how to derive them, and how to use them to approximate functions. We will also discuss the limitations of Taylor series and how to overcome them. This article is aimed at beginners who are interested in calculus and want to learn more about Taylor series."
|
||||
type = "article"
|
||||
authors = ["Youwen Wu"]
|
||||
|
||||
[manifest.tags]
|
||||
primary = ["math", "calculus"]
|
||||
secondary = ["taylor series", "numerical methods", "approximation"]
|
||||
|
||||
[manifest.authors]
|
||||
["Youwen Wu"]
|
||||
|
||||
[cover]
|
||||
src = "https://i.ytimg.com/vi/3d6DsjIBzJ4/maxresdefault.jpg"
|
||||
alt = "visual of taylor series for a quadratic function"
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
title = "My Awesome Blog Post"
|
||||
|
||||
[manifest]
|
||||
date = 2024-01-01T12:00:00Z
|
||||
author = "Youwen Wu"
|
||||
date = 2024-04-12T20:48:40.799Z
|
||||
authors = ["Youwen Wu"]
|
||||
blurb = "A blog post about Rust and WebAssembly"
|
||||
description = "A blog post about Rust and WebAssembly, and how to use it in web development. This post will cover the basics of Rust and WebAssembly, and how to use it in web development. We will also discuss the benefits of using Rust and WebAssembly, and how it can help you build faster and more efficient web applications."
|
||||
type = "article"
|
||||
|
||||
[manifest.tags]
|
||||
primary = ["Rust", "WebAssembly"]
|
||||
|
|
18
src/globals.d.ts
vendored
18
src/globals.d.ts
vendored
|
@ -1,5 +1,5 @@
|
|||
interface BlogDocument {
|
||||
time: number;
|
||||
date: Date;
|
||||
title: string;
|
||||
content: string;
|
||||
primaryTags: string[];
|
||||
|
@ -7,6 +7,22 @@ interface BlogDocument {
|
|||
blurb: string;
|
||||
image?: string;
|
||||
description: string;
|
||||
slug;
|
||||
}
|
||||
|
||||
declare module 'remark-sectionize';
|
||||
|
||||
type PostMeta = {
|
||||
title: string;
|
||||
manifest: {
|
||||
authors: string[];
|
||||
date: Date;
|
||||
tags: { primary: string[]; secondary: string[] };
|
||||
blurb: string;
|
||||
description: string;
|
||||
};
|
||||
cover: {
|
||||
src: string;
|
||||
alt: string;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { ChevronRight } from 'svelte-radix';
|
||||
import PostMetadata from './PostMetadata.svelte';
|
||||
import Crumbs from './Crumbs.svelte';
|
||||
|
||||
export let doc: BlogDocument;
|
||||
</script>
|
||||
|
||||
<article>
|
||||
<div class="mb-4 flex items-center space-x-1 text-sm text-muted-foreground">
|
||||
<div class="overflow-hidden text-ellipsis whitespace-nowrap">The Coredump</div>
|
||||
<ChevronRight class="h-4 w-4" />
|
||||
<div class="font-medium text-foreground">{doc.title}</div>
|
||||
</div>
|
||||
<Crumbs slug={doc.slug} title={doc.title} />
|
||||
<header class="space-y-6">
|
||||
<div class="space-y-2">
|
||||
<h1 class="scroll-m-20 text-5xl font-bold font-serif tracking-tight">
|
||||
|
@ -22,7 +19,7 @@
|
|||
<PostMetadata
|
||||
primaryTags={doc.primaryTags}
|
||||
secondaryTags={doc.secondaryTags}
|
||||
time={doc.time}
|
||||
date={doc.date}
|
||||
length={doc.content.length}
|
||||
reverseDateAndRest
|
||||
/>
|
||||
|
|
20
src/lib/components/Blog/Crumbs.svelte
Normal file
20
src/lib/components/Blog/Crumbs.svelte
Normal file
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { ChevronRight } from 'svelte-radix';
|
||||
|
||||
export let slug: string;
|
||||
export let title: string;
|
||||
|
||||
$: trail = slug.split('/').slice(0, slug.split('/').length - 1);
|
||||
</script>
|
||||
|
||||
<div class="mb-4 flex items-center space-x-1 text-sm text-muted-foreground">
|
||||
<a class="overflow-hidden text-ellipsis whitespace-nowrap hover:underline" href="/blog"
|
||||
>The Coredump</a
|
||||
>
|
||||
<ChevronRight class="h-4 w-4" />
|
||||
{#each trail as crumb, i}
|
||||
<div class="text-muted-foreground">{crumb}</div>
|
||||
<ChevronRight class="h-4 w-4" />
|
||||
{/each}
|
||||
<div class="font-medium text-foreground">{title}</div>
|
||||
</div>
|
|
@ -5,17 +5,23 @@
|
|||
import PostMetadata from './PostMetadata.svelte';
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
export let doc: BlogDocument;
|
||||
export let doc: {
|
||||
metadata: PostMeta;
|
||||
content: string;
|
||||
slug: string;
|
||||
};
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<h3 class="text-3xl lg:text-4xl font-serif font-bold mb-4 leading-tight">{doc.title}</h3>
|
||||
<p class="text-muted-foreground text-xl">{doc.blurb}</p>
|
||||
<h3 class="text-3xl lg:text-4xl font-serif font-bold mb-4 leading-tight">
|
||||
{doc.metadata.title}
|
||||
</h3>
|
||||
<p class="text-muted-foreground text-xl">{doc.metadata.manifest.blurb}</p>
|
||||
<PostMetadata
|
||||
primaryTags={doc.primaryTags}
|
||||
secondaryTags={doc.secondaryTags}
|
||||
time={doc.time}
|
||||
primaryTags={doc.metadata.manifest.tags.primary}
|
||||
secondaryTags={doc.metadata.manifest.tags.secondary}
|
||||
date={doc.metadata.manifest.date}
|
||||
length={doc.content.split(' ').length}
|
||||
/>
|
||||
</Card.Header>
|
||||
|
@ -26,7 +32,7 @@
|
|||
class="col-span-3 md:col-span-1 rounded-2xl shadow-md"
|
||||
/>
|
||||
<div class="flex flex-col justify-around col-span-3 md:col-span-2 gap-4">
|
||||
<p class="text-primary/95 font-serif leading-relaxed">{doc.description}</p>
|
||||
<p class="text-primary/95 font-serif leading-relaxed">{doc.metadata.manifest.description}</p>
|
||||
<Button variant="outline" href="/blog/2024/test-post" class="text-xl flex-grow sm:flex-grow-0"
|
||||
>Read More</Button
|
||||
>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
- `primaryTags` - An array of strings representing the primary tags of the post.
|
||||
- `secondaryTags` - An array of strings representing the secondary tags of the post.
|
||||
- `time` - A unix epoch integer representing the time the post was published.
|
||||
- `date` -
|
||||
- `length` - An integer representing amount of words in the post.
|
||||
- `reverseDateAndRest` - A boolean that determines whether the date should be displayed at the bottom of the metadata.
|
||||
|
||||
|
@ -20,21 +20,22 @@
|
|||
import Badge from '../ui/badge/badge.svelte';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export let primaryTags: string[] = [];
|
||||
export let secondaryTags: string[] = [];
|
||||
export let time: number;
|
||||
export let date: Date;
|
||||
export let length: number;
|
||||
export let reverseDateAndRest: boolean = false;
|
||||
|
||||
let date = dayjs(time * 1000);
|
||||
let dayjsDate = dayjs(date);
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1">
|
||||
{#if !reverseDateAndRest}
|
||||
<p class="text-muted-foreground/80 my-1 text-lg">{date.format('MMMM DD, YYYY')}</p>
|
||||
<p class="text-muted-foreground/80 my-1 text-lg">{dayjsDate.format('MMMM DD, YYYY')}</p>
|
||||
{/if}
|
||||
<span class="flex items-center flex-wrap my-2 gap-2">
|
||||
{#each primaryTags as tag}
|
||||
|
@ -46,9 +47,9 @@
|
|||
</span>
|
||||
<!-- Assuming adult silent reading rate of 238 wpm -->
|
||||
<span class="text-muted-foreground text-sm mt-2">
|
||||
{dayjs(date).fromNow()} | {Math.ceil(length / 238)} min read | {length} words
|
||||
{dayjsDate.fromNow()} | {Math.ceil(length / 238)} min read | {length} words
|
||||
</span>
|
||||
{#if reverseDateAndRest}
|
||||
<p class="text-muted-foreground/80 text-xl mt-4">{date.format('MMMM DD, YYYY')}</p>
|
||||
<p class="text-muted-foreground/80 text-xl mt-4">{dayjsDate.format('MMMM DD, YYYY')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -26,7 +26,9 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<nav class="h-24 bg-background bg-opacity-50 backdrop-blur-lg fixed w-full z-40 font-display">
|
||||
<nav
|
||||
class="h-16 lg:h-24 bg-background bg-opacity-50 backdrop-blur-lg fixed w-full z-40 font-display"
|
||||
>
|
||||
<div class="container mx-auto flex justify-between items-center h-full gap-6 overflow-x-auto">
|
||||
<Drawer />
|
||||
{#if current === 'blog'}
|
||||
|
|
|
@ -23,11 +23,11 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<nav class="fixed top-24 left-0 w-full bg-background bg-opacity-50 backdrop-blur-lg z-30 lg:hidden">
|
||||
<nav class="fixed top-16 left-0 w-full bg-background bg-opacity-50 backdrop-blur-lg z-30 lg:hidden">
|
||||
<Accordion.Root class="px-8" bind:value>
|
||||
<Accordion.Item value="toc">
|
||||
<Accordion.Trigger
|
||||
class="text-xl flex w-full flex-1 items-center justify-between py-1 font-medium transition-all [&[data-state=open]>span>svg]:rotate-180"
|
||||
class="text-lg font-medium flex w-full flex-1 items-center justify-between py-1 transition-all [&[data-state=open]>span>svg]:rotate-180"
|
||||
>{currentDisplayed}
|
||||
<span
|
||||
class="inline-flex size-8 items-center justify-center rounded-[7px] bg-transparent transition-all hover:bg-dark-10"
|
||||
|
|
|
@ -2,22 +2,7 @@ import { readdir, readFile } from 'fs/promises';
|
|||
import { join } from 'path';
|
||||
import toml from 'toml';
|
||||
|
||||
export type PostMeta = {
|
||||
title: string;
|
||||
manifest: {
|
||||
authors: string[];
|
||||
date: Date;
|
||||
tags: { primary: string[]; secondary: string[] };
|
||||
blurb: string;
|
||||
description: string;
|
||||
};
|
||||
cover: {
|
||||
src: string;
|
||||
alt: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default async () => {
|
||||
const crawl = async () => {
|
||||
const blogPath = join(process.cwd(), 'blog');
|
||||
const years = await readdir(blogPath);
|
||||
|
||||
|
@ -54,3 +39,8 @@ export default async () => {
|
|||
return posts;
|
||||
}
|
||||
};
|
||||
|
||||
const postsResult = await crawl();
|
||||
if (!postsResult) throw new Error('No posts found!');
|
||||
|
||||
export const posts = postsResult;
|
||||
|
|
8
src/routes/blog/+page.server.ts
Normal file
8
src/routes/blog/+page.server.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { posts } from '$lib/utils/crawl';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
posts
|
||||
};
|
||||
};
|
|
@ -13,23 +13,19 @@
|
|||
import { faker } from '@faker-js/faker';
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
$: posts = data.posts;
|
||||
|
||||
let loaded = false;
|
||||
|
||||
onMount(() => {
|
||||
loaded = true;
|
||||
});
|
||||
|
||||
let testDoc: BlogDocument = {
|
||||
title: 'An introduction to Taylor Series',
|
||||
primaryTags: ['Computer Science', 'Mathematics'],
|
||||
secondaryTags: ['Calculus', 'Taylor Series'],
|
||||
time: Date.now() / 1000,
|
||||
content:
|
||||
"This is a test post to test the layout and rendering of the blog. It's taken directly from my obsidian notebook, so some of the formatting may be off. The blog supports KaTeX math rendering and GitHub markdown alerts. Support for syntax highlighting and graphs is coming soon.",
|
||||
blurb: 'A brief introduction to Taylor Series and its applications in calculus.',
|
||||
description:
|
||||
'An insightful and longer description of the post. This should be a bit more detailed than the blurb. It should give the reader a good idea of what the post is about.'
|
||||
};
|
||||
$: postArray = Object.entries(posts).map(([key, value]) => {
|
||||
return { slug: key, ...value };
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
@ -46,14 +42,14 @@
|
|||
class="text-lg sm:text-xl md:text-2xl text-muted-foreground font-mono text-center sm:text-left"
|
||||
in:fly={{ duration: 300, y: -50, delay: 200 }}
|
||||
>
|
||||
my blog on computer science, math, games, art, and more.
|
||||
ramblings on computer science, math, games, and more.
|
||||
</p>
|
||||
<span
|
||||
class="flex flex-wrap items-center mt-8 md:hidden"
|
||||
in:fly={{ duration: 300, x: -50, delay: 300 }}
|
||||
>
|
||||
<a href="#archive" class="flex items-center font-mono gap-2 hover:underline"
|
||||
><ChevronRight />Archived Posts</a
|
||||
><ChevronRight />Archive</a
|
||||
>
|
||||
</span>
|
||||
|
||||
|
@ -66,39 +62,13 @@
|
|||
>
|
||||
Latest Posts
|
||||
</h2>
|
||||
{#each Array(5) as _, i}
|
||||
{#if i > 0}
|
||||
{#each postArray as post, i}
|
||||
<div
|
||||
class="grid grid-cols-1 gap-4 mt-8"
|
||||
in:fly|global={{ x: -50, delay: 350 + i * 100 }}
|
||||
>
|
||||
<PostCard
|
||||
doc={{
|
||||
title:
|
||||
faker.hacker.noun() +
|
||||
' ' +
|
||||
faker.hacker.verb() +
|
||||
' ' +
|
||||
faker.hacker.adjective() +
|
||||
' ' +
|
||||
faker.hacker.noun(),
|
||||
primaryTags: Array(2).map(() => faker.hacker.noun()),
|
||||
secondaryTags: Array(2).map(() => faker.hacker.noun()),
|
||||
time: Date.now() / 1000,
|
||||
content: faker.lorem.paragraphs(5),
|
||||
blurb: faker.hacker.phrase(),
|
||||
description: faker.lorem.paragraphs(2)
|
||||
}}
|
||||
/>
|
||||
<PostCard doc={post} />
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="grid grid-cols-1 gap-4 mt-8"
|
||||
in:fly|global={{ x: -50, delay: 350 + i * 100 }}
|
||||
>
|
||||
<PostCard doc={testDoc} />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div in:fly={{ y: -50, delay: 300 }} class="col-span-3 md:col-span-1">
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import crawl from '$lib/utils/crawl';
|
||||
import { posts } from '$lib/utils/crawl';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { EntryGenerator, PageServerLoad } from './$types';
|
||||
import parseMarkdown from '$lib/utils/parseMarkdown';
|
||||
|
||||
const posts = await crawl();
|
||||
if (!posts) throw new Error('No posts found!');
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const post = posts[params.slug];
|
||||
|
||||
|
@ -17,7 +14,8 @@ export const load: PageServerLoad = async ({ params }) => {
|
|||
|
||||
return {
|
||||
...post,
|
||||
content: parsed
|
||||
content: parsed,
|
||||
slug: params.slug
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { toc, createTocStore } from '@svelte-put/toc';
|
||||
import StickyToc from '$lib/components/Toc/StickyToc.svelte';
|
||||
import TocHeader from '$lib/components/Toc/TocHeader.svelte';
|
||||
import { DateRangeField } from 'bits-ui';
|
||||
|
||||
const tocStore = createTocStore();
|
||||
|
||||
|
@ -13,10 +14,11 @@
|
|||
title: data.metadata.title,
|
||||
primaryTags: data.metadata.manifest.tags.primary,
|
||||
secondaryTags: data.metadata.manifest.tags.secondary,
|
||||
time: data.metadata.manifest.date.getTime() / 1000,
|
||||
date: data.metadata.manifest.date,
|
||||
content: data.content,
|
||||
blurb: data.metadata.manifest.blurb,
|
||||
description: data.metadata.manifest.description
|
||||
description: data.metadata.manifest.description,
|
||||
slug: data.slug
|
||||
};
|
||||
// $: doc = data.metadata;
|
||||
// $: componentSource = data.metadata.source?.replace('default', $config.style ?? 'default');
|
||||
|
|
Loading…
Reference in a new issue