diff --git a/static/test.md b/blog/2024/taylor-series/content.md similarity index 100% rename from static/test.md rename to blog/2024/taylor-series/content.md diff --git a/blog/2024/taylor-series/post.toml b/blog/2024/taylor-series/post.toml new file mode 100644 index 0000000..b366df1 --- /dev/null +++ b/blog/2024/taylor-series/post.toml @@ -0,0 +1,18 @@ +title = "A Brief Introduction to Taylor Series" + +[manifest] +date = 2024-01-01T12:00:00Z +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." + +[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" +caption = "Taylor series approximation of a quadratic function." diff --git a/blog/2024/test-post/content.md b/blog/2024/test-post/content.md new file mode 100644 index 0000000..7927937 --- /dev/null +++ b/blog/2024/test-post/content.md @@ -0,0 +1,3 @@ +## This is some test content + +Whatever gets written here will be rendered statically! diff --git a/blog/2024/test-post/post.toml b/blog/2024/test-post/post.toml new file mode 100644 index 0000000..25fade9 --- /dev/null +++ b/blog/2024/test-post/post.toml @@ -0,0 +1,16 @@ +title = "My Awesome Blog Post" + +[manifest] +date = 2024-01-01T12:00:00Z +author = "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." + +[manifest.tags] +primary = ["Rust", "WebAssembly"] +secondary = ["Web Development", "Frontend"] + +[cover] +src = "cover.jpg" +alt = "A Rust and WebAssembly logo" +caption = "Rust and WebAssembly" diff --git a/package.json b/package.json index 480a77a..39c4276 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@fontsource/zilla-slab": "^5.0.12", "@svelte-put/toc": "^5.0.1", "@sveltejs/adapter-vercel": "^5.2.0", + "@types/node": "^20.12.5", "bits-ui": "^0.21.2", "clsx": "^2.1.0", "dayjs": "^1.11.10", @@ -65,6 +66,7 @@ "svelte-sonner": "^0.3.21", "tailwind-merge": "^2.2.2", "tailwind-variants": "^0.2.1", + "toml": "^3.0.0", "unified": "^11.0.4", "vaul-svelte": "^0.3.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 447768b..13c8e33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: '@sveltejs/adapter-vercel': specifier: ^5.2.0 version: 5.2.0(@sveltejs/kit@2.5.5) + '@types/node': + specifier: ^20.12.5 + version: 20.12.5 bits-ui: specifier: ^0.21.2 version: 0.21.2(svelte@4.2.12) @@ -71,6 +74,9 @@ dependencies: tailwind-variants: specifier: ^0.2.1 version: 0.2.1(tailwindcss@3.4.3) + toml: + specifier: ^3.0.0 + version: 3.0.0 unified: specifier: ^11.0.4 version: 11.0.4 @@ -141,7 +147,7 @@ devDependencies: version: 5.4.4 vite: specifier: ^5.0.3 - version: 5.2.8 + version: 5.2.8(@types/node@20.12.5) packages: @@ -956,7 +962,7 @@ packages: sirv: 2.0.4 svelte: 4.2.12 tiny-glob: 0.2.9 - vite: 5.2.8 + vite: 5.2.8(@types/node@20.12.5) /@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.2.8): resolution: {integrity: sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==} @@ -969,7 +975,7 @@ packages: '@sveltejs/vite-plugin-svelte': 3.0.2(svelte@4.2.12)(vite@5.2.8) debug: 4.3.4 svelte: 4.2.12 - vite: 5.2.8 + vite: 5.2.8(@types/node@20.12.5) transitivePeerDependencies: - supports-color @@ -987,7 +993,7 @@ packages: magic-string: 0.30.9 svelte: 4.2.12 svelte-hmr: 0.15.3(svelte@4.2.12) - vite: 5.2.8 + vite: 5.2.8(@types/node@20.12.5) vitefu: 0.2.5(vite@5.2.8) transitivePeerDependencies: - supports-color @@ -1041,6 +1047,11 @@ packages: resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} dev: false + /@types/node@20.12.5: + resolution: {integrity: sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==} + dependencies: + undici-types: 5.26.5 + /@types/pug@2.0.10: resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} dev: true @@ -3806,6 +3817,10 @@ packages: dependencies: is-number: 7.0.0 + /toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + dev: false + /totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -3855,6 +3870,9 @@ packages: hasBin: true dev: true + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /unified@11.0.4: resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} dependencies: @@ -3993,7 +4011,7 @@ packages: vfile-message: 4.0.2 dev: false - /vite@5.2.8: + /vite@5.2.8(@types/node@20.12.5): resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -4021,6 +4039,7 @@ packages: terser: optional: true dependencies: + '@types/node': 20.12.5 esbuild: 0.20.2 postcss: 8.4.38 rollup: 4.14.0 @@ -4035,7 +4054,7 @@ packages: vite: optional: true dependencies: - vite: 5.2.8 + vite: 5.2.8(@types/node@20.12.5) /web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} diff --git a/src/app.html b/src/app.html index 66dad16..9c1b021 100644 --- a/src/app.html +++ b/src/app.html @@ -2,6 +2,7 @@ + {doc.title} - {#if doc.description} -

- {doc.description} -

- {/if} +

+ {doc.blurb} +

- -
+
{@html doc.content}
diff --git a/src/lib/components/Toc/StickyToc.svelte b/src/lib/components/Toc/StickyToc.svelte index c37f8a1..2e43a5f 100644 --- a/src/lib/components/Toc/StickyToc.svelte +++ b/src/lib/components/Toc/StickyToc.svelte @@ -14,7 +14,7 @@ -

On this page

+

On this page

{#if $tocStore.items.size} diff --git a/src/lib/styles/markdown.css b/src/lib/styles/markdown.css index c8d0cc9..eec04fb 100644 --- a/src/lib/styles/markdown.css +++ b/src/lib/styles/markdown.css @@ -1,6 +1,5 @@ .markdown-body { @apply text-primary/90; - h1, h2 { @apply text-3xl font-serif mb-5 mt-16; } diff --git a/src/lib/utils/crawl.ts b/src/lib/utils/crawl.ts new file mode 100644 index 0000000..5140b92 --- /dev/null +++ b/src/lib/utils/crawl.ts @@ -0,0 +1,56 @@ +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 blogPath = join(process.cwd(), 'blog'); + const years = await readdir(blogPath); + + const posts: { + [key: string]: { + metadata: PostMeta; + content: string; + }; + } = {}; + + for (const year of years) { + const yearPath = join(blogPath, year); + const directories = await readdir(yearPath); + + for (const directory of directories) { + const postPath = join(yearPath, directory); + const postTomlPath = join(postPath, 'post.toml'); + const contentPath = join(postPath, 'content.md'); + + try { + const postToml = await readFile(postTomlPath, 'utf-8'); + const content = await readFile(contentPath, 'utf-8'); + + const metadata = toml.parse(postToml); + + posts[`${year}/${directory}`] = { + metadata, + content + }; + } catch (error) { + throw new Error(`Error reading post: ${error}`); + } + } + return posts; + } +}; diff --git a/src/lib/utils/parseMarkdown.ts b/src/lib/utils/parseMarkdown.ts new file mode 100644 index 0000000..103f33f --- /dev/null +++ b/src/lib/utils/parseMarkdown.ts @@ -0,0 +1,32 @@ +import { unified } from 'unified'; +import rehypeKatex from 'rehype-katex'; +import rehypeStringify from 'rehype-stringify'; +import remarkMath from 'remark-math'; +import remarkParse from 'remark-parse'; +import remarkRehype from 'remark-rehype'; +import remarkGfm from 'remark-gfm'; +import remarkGhAlerts from 'remark-gh-alerts'; +import remarkSectionize from 'remark-sectionize'; + +export default async (markdown: string) => { + try { + const html = String( + await unified() + .use(remarkParse) + .use(remarkSectionize) + .use(remarkGfm) + .use(remarkGhAlerts) + .use(remarkMath) + .use(remarkRehype) + .use(rehypeKatex) + .use(rehypeStringify) + .process(markdown) + ); + + if (typeof html === 'undefined') throw new Error('No content'); + + return html; + } catch (e) { + throw new Error(`Failed to parse markdown: ${e}`); + } +}; diff --git a/src/routes/blog/+page.server.ts b/src/routes/blog/+page.server.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/routes/blog/[...slug]/+page.server.ts b/src/routes/blog/[...slug]/+page.server.ts index 900a22f..c87e875 100644 --- a/src/routes/blog/[...slug]/+page.server.ts +++ b/src/routes/blog/[...slug]/+page.server.ts @@ -1,5 +1,26 @@ -import type { EntryGenerator } from './$types'; +import crawl 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]; + + if (!post) { + error(404, 'Post not found.'); + } + + const parsed = await parseMarkdown(post.content); + + return { + ...post, + content: parsed + }; +}; export const entries: EntryGenerator = () => { - return [{ slug: '2024/test-post' }, { slug: '2024/another-post' }]; + return Object.keys(posts).map((slug) => ({ slug })); }; diff --git a/src/routes/blog/[...slug]/+page.svelte b/src/routes/blog/[...slug]/+page.svelte index b53ce34..eaf2117 100644 --- a/src/routes/blog/[...slug]/+page.svelte +++ b/src/routes/blog/[...slug]/+page.svelte @@ -10,14 +10,13 @@ export let data: PageData; let doc: BlogDocument = { - title: 'Test Post', - primaryTags: ['Computer Science', 'Mathematics'], - secondaryTags: ['Calculus', 'Taylor Series'], - time: Date.now() / 1000, - content: data.content!, - blurb: 'A short and succinct, yet descriptive blurb about the post.', - 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.' + title: data.metadata.title, + primaryTags: data.metadata.manifest.tags.primary, + secondaryTags: data.metadata.manifest.tags.secondary, + time: data.metadata.manifest.date.getTime() / 1000, + content: data.content, + blurb: data.metadata.manifest.blurb, + description: data.metadata.manifest.description }; // $: doc = data.metadata; // $: componentSource = data.metadata.source?.replace('default', $config.style ?? 'default'); diff --git a/src/routes/blog/[...slug]/+page.ts b/src/routes/blog/[...slug]/+page.ts index 977c0e5..189f71e 100644 --- a/src/routes/blog/[...slug]/+page.ts +++ b/src/routes/blog/[...slug]/+page.ts @@ -1,40 +1 @@ -import type { PageLoad } from '../[year]/[slug]/$types.js'; -import { unified } from 'unified'; -import rehypeKatex from 'rehype-katex'; -import rehypeStringify from 'rehype-stringify'; -import remarkMath from 'remark-math'; -import remarkParse from 'remark-parse'; -import remarkRehype from 'remark-rehype'; -import remarkGfm from 'remark-gfm'; -import remarkGhAlerts from 'remark-gh-alerts'; -import remarkSectionize from 'remark-sectionize'; - export const prerender = true; - -export const load: PageLoad = async ({ fetch }) => { - try { - const data = await fetch('/test.md'); - const content = String( - await unified() - .use(remarkParse) - .use(remarkSectionize) - .use(remarkGfm) - .use(remarkGhAlerts) - .use(remarkMath) - .use(remarkRehype) - .use(rehypeKatex) - .use(rehypeStringify) - .process(await data.text()) - ); - - if (typeof content === 'undefined') throw new Error('No content'); - - return { - content - }; - } catch (e) { - return { - markdown: `Error: ${e}` - }; - } -}; diff --git a/vite.config.ts b/vite.config.ts index bbf8c7d..53b35ba 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,5 +2,10 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ - plugins: [sveltekit()] + plugins: [sveltekit()], + server: { + fs: { + allow: ['./blog'] + } + } });