diff options
| -rw-r--r-- | .vscode/settings.json | 5 | ||||
| -rw-r--r-- | src/app.css | 35 | ||||
| -rw-r--r-- | src/app.html | 1 | ||||
| -rw-r--r-- | src/lib/blog/Post.svelte | 19 | ||||
| -rw-r--r-- | src/lib/blog/Post.ts | 28 | ||||
| -rw-r--r-- | src/params/int.ts | 3 | ||||
| -rw-r--r-- | src/routes/blog/+page.server.ts | 13 | ||||
| -rw-r--r-- | src/routes/blog/+page.svelte | 13 | ||||
| -rw-r--r-- | src/routes/blog/[id=int]-[slug]/+page.server.ts | 2 | ||||
| -rw-r--r-- | src/routes/blog/[id=int]-[slug]/+page.svelte | 8 | ||||
| -rw-r--r-- | src/routes/blog/[id=int]-[slug]/+page.ts | 2 | ||||
| -rw-r--r-- | src/routes/blog/[id=int]/+page.server.ts | 12 | ||||
| -rw-r--r-- | src/routes/blog/[id=int]/+page.svelte | 28 | ||||
| -rw-r--r-- | src/routes/blog/[id=int]/+page.ts | 11 | ||||
| -rw-r--r-- | src/routes/blog/base-post.svx | 15 | ||||
| -rw-r--r-- | src/routes/blog/dynamic-posts.ts | 4 | ||||
| -rw-r--r-- | src/routes/blog/posts.ts | 6 | ||||
| -rw-r--r-- | src/routes/blog/posts/test-post.svx | 27 | ||||
| -rw-r--r-- | svelte.config.js | 6 |
19 files changed, 236 insertions, 2 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6bb5266 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "*.css": "tailwindcss", + }, +} diff --git a/src/app.css b/src/app.css index 43f5ab3..49c5f3a 100644 --- a/src/app.css +++ b/src/app.css @@ -10,6 +10,10 @@ @import 'tailwindcss'; @theme { + --font-sans: + InterVariable, Inter, "Noto Sans", + ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-bios: 'Ac437 IBM EGA 8x14', 'Hack', 'Courier New', 'Courier', 'monospace'; --font-grub: @@ -19,6 +23,36 @@ 'PxPlus IBM EGA 8x14', 'Hack', 'Courier New', 'Courier', 'monospace'; } +#postmd { + @apply font-sans; + h1 { + @apply font-bios text-5xl mb-1.5 mt-2; + } + h2 { + @apply my-1.5 text-2xl; + } + h3 { + @apply my-1 text-xl; + } + h4 { + @apply my-1 text-lg; + } + h1,h2,h3,h4,h5 { + @apply relative; + &::before { + content: ""; + @apply absolute top-[20%] left-0 h-[60%] w-1 -ml-3 rounded-full transition-all bg-gray-200/0; + } + &:hover::before, + &:has(+p:hover)::before { + @apply -ml-2 bg-gray-200; + } + } + p { + @apply my-1; + } +} + /* The default border color has changed to `currentColor` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still @@ -28,7 +62,6 @@ color utility to any element that depends on these defaults. */ @layer base { - *, ::after, ::before, diff --git a/src/app.html b/src/app.html index 352cba4..2d05f4f 100644 --- a/src/app.html +++ b/src/app.html @@ -20,6 +20,7 @@ <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link rel="icon" href="%sveltekit.assets%/favicon.webp" /> + <link rel="stylesheet" href="%sveltekit.assets%/inter/inter.css" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> %sveltekit.head% </head> diff --git a/src/lib/blog/Post.svelte b/src/lib/blog/Post.svelte new file mode 100644 index 0000000..20178e4 --- /dev/null +++ b/src/lib/blog/Post.svelte @@ -0,0 +1,19 @@ +<script lang="ts" module> + export * from './Post'; +</script> + +<script lang="ts"> + import type { Post } from './Post'; + + let { + post, + }: { + post: Post; + } = $props(); + + let PostComp = $derived(post.default); +</script> + +<div id="postmd"> + <PostComp /> +</div> diff --git a/src/lib/blog/Post.ts b/src/lib/blog/Post.ts new file mode 100644 index 0000000..440dffa --- /dev/null +++ b/src/lib/blog/Post.ts @@ -0,0 +1,28 @@ + +import type { Component } from 'svelte'; + +export type PostMetadata<Parsed extends boolean = false> = { + title: string; + blurb: string; + author: string | null; + slug: string; + id: string | number; + created: Parsed extends true ? Date : string; + updated: Parsed extends true ? Date : string; +}; +export type Post<MetadataParsed extends boolean = false> = { + metadata: PostMetadata<MetadataParsed>; + default: Component; +}; + +export const parsePostMetadata = ( + m: PostMetadata<boolean>, +): PostMetadata<true> => ({ + ...m, + created: new Date(m.created), + updated: new Date(m.updated), +}); +export const parsePost = (p: Post<boolean>): Post<true> => ({ + ...p, + metadata: parsePostMetadata(p.metadata), +}); diff --git a/src/params/int.ts b/src/params/int.ts new file mode 100644 index 0000000..3d77400 --- /dev/null +++ b/src/params/int.ts @@ -0,0 +1,3 @@ +import type { ParamMatcher } from '@sveltejs/kit'; + +export const match = ((param: string): param is `${number}` => !isNaN(parseInt(param))) satisfies ParamMatcher; diff --git a/src/routes/blog/+page.server.ts b/src/routes/blog/+page.server.ts new file mode 100644 index 0000000..7c4726e --- /dev/null +++ b/src/routes/blog/+page.server.ts @@ -0,0 +1,13 @@ +import { parsePost, type Post } from '$/lib/blog/Post.svelte' + +const posts = import.meta.glob("./posts/*.svx") as Record<string, () => Promise<Post>> +const returnedData = Promise.all(Object.entries(posts).map(v => v[1]().then(r => [v[0], { + ...parsePost(r as Post), + default: null, +}] as const))); + +export const load = async () => { + return { + posts: Object.fromEntries(await returnedData) + } +} diff --git a/src/routes/blog/+page.svelte b/src/routes/blog/+page.svelte new file mode 100644 index 0000000..a817d44 --- /dev/null +++ b/src/routes/blog/+page.svelte @@ -0,0 +1,13 @@ +<script lang="ts"> + import type { PageProps } from './$types'; + + let { data }: PageProps = $props(); +</script> + +<div class="font-mono"> + {#each Object.entries(data.posts) as [_filename, post]} + {JSON.stringify(post.metadata, (k, v) => + v instanceof Date ? v.toISOString() : v, + )} + {/each} +</div> diff --git a/src/routes/blog/[id=int]-[slug]/+page.server.ts b/src/routes/blog/[id=int]-[slug]/+page.server.ts new file mode 100644 index 0000000..be8b7c0 --- /dev/null +++ b/src/routes/blog/[id=int]-[slug]/+page.server.ts @@ -0,0 +1,2 @@ +import { load as pageload } from '../[id=int]/+page.server'; +export const load = pageload; diff --git a/src/routes/blog/[id=int]-[slug]/+page.svelte b/src/routes/blog/[id=int]-[slug]/+page.svelte new file mode 100644 index 0000000..088ae00 --- /dev/null +++ b/src/routes/blog/[id=int]-[slug]/+page.svelte @@ -0,0 +1,8 @@ +<script lang="ts"> + import type { PageProps } from './$types'; + import Page from '../[id=int]/+page.svelte'; + + let props: PageProps = $props(); +</script> + +<Page {...props} /> diff --git a/src/routes/blog/[id=int]-[slug]/+page.ts b/src/routes/blog/[id=int]-[slug]/+page.ts new file mode 100644 index 0000000..1b54560 --- /dev/null +++ b/src/routes/blog/[id=int]-[slug]/+page.ts @@ -0,0 +1,2 @@ +import { load as pageload } from '../[id=int]/+page'; +export const load = pageload; diff --git a/src/routes/blog/[id=int]/+page.server.ts b/src/routes/blog/[id=int]/+page.server.ts new file mode 100644 index 0000000..e295257 --- /dev/null +++ b/src/routes/blog/[id=int]/+page.server.ts @@ -0,0 +1,12 @@ +import { parsePost } from '$/lib/blog/Post.svelte' +import rawPosts from '../posts.js'; + +const posts = Promise.all(Object.entries(rawPosts).map(async post => [post[0], parsePost(await post[1])] as const)); +const postMap = posts.then(posts => posts.map(post => [post[1].metadata.id.toString(), { + metadata: post[1].metadata, + filename: post[0], +}])).then(v => new Map(v)); + +export const load = async (req) => ({ + post: (await postMap).get(req.params.id), +}); diff --git a/src/routes/blog/[id=int]/+page.svelte b/src/routes/blog/[id=int]/+page.svelte new file mode 100644 index 0000000..16fc586 --- /dev/null +++ b/src/routes/blog/[id=int]/+page.svelte @@ -0,0 +1,28 @@ +<script lang="ts"> + import { replaceState } from '$app/navigation'; + import { onMount, tick } from 'svelte'; + + import type { PageProps } from './$types'; + import { resolve } from '$app/paths'; + import { page } from '$app/state'; + import { forceTrailingSlash } from '$/lib'; + import Post from '$/lib/blog/Post.svelte'; + + let { data }: PageProps = $props(); + + onMount(() => { + tick().then(() => + replaceState( + forceTrailingSlash( + resolve('/blog/[id=int]-[slug]', { + id: data.post.metadata.id.toString(), + slug: data.post.metadata.slug, + }), + ), + page.state, + ), + ); + }); +</script> + +<Post post={data.post} /> diff --git a/src/routes/blog/[id=int]/+page.ts b/src/routes/blog/[id=int]/+page.ts new file mode 100644 index 0000000..077d72c --- /dev/null +++ b/src/routes/blog/[id=int]/+page.ts @@ -0,0 +1,11 @@ +import { error } from '@sveltejs/kit'; +import posts from '../dynamic-posts.js'; + +export const load = async ({ data }) => { + const filename = data.post?.filename; + if (!filename) throw error(404, 'Post not found.') + const post = (await posts[filename]()); + return { + post, + } +} diff --git a/src/routes/blog/base-post.svx b/src/routes/blog/base-post.svx new file mode 100644 index 0000000..394eaa2 --- /dev/null +++ b/src/routes/blog/base-post.svx @@ -0,0 +1,15 @@ +--- +title: "Base Post" +blurb: "Awawawa Ipsum dolor the neobot is in the washing machine" +author: "7222e800" +slug: "base-post" +id: -1 + +# Timestamps are in ISO8601 UTC (`date -u +%Y-%m-%dT%H:%M:%SZ`) +created: "2026-01-14T01:25:14Z" +updated: "2026-01-14T01:25:14Z" +--- + +# Base Post + +This is an example base post. diff --git a/src/routes/blog/dynamic-posts.ts b/src/routes/blog/dynamic-posts.ts new file mode 100644 index 0000000..93ecfec --- /dev/null +++ b/src/routes/blog/dynamic-posts.ts @@ -0,0 +1,4 @@ +import type { Post } from '$/lib/blog/Post.svelte'; + +export const posts = import.meta.glob("./posts/*.svx") as Record<string, () => Promise<Post>> +export default posts diff --git a/src/routes/blog/posts.ts b/src/routes/blog/posts.ts new file mode 100644 index 0000000..5336bb0 --- /dev/null +++ b/src/routes/blog/posts.ts @@ -0,0 +1,6 @@ +import type { Post } from '$/lib/blog/Post.svelte'; + +export const posts = import.meta.glob("./posts/*.svx", { + eager: true +}) as Record<string, Promise<Post>> +export default posts diff --git a/src/routes/blog/posts/test-post.svx b/src/routes/blog/posts/test-post.svx new file mode 100644 index 0000000..418c6e0 --- /dev/null +++ b/src/routes/blog/posts/test-post.svx @@ -0,0 +1,27 @@ +--- +title: "Test Post" +blurb: "Awawawa Ipsum dolor the neobot is in the washing machine" +author: "7222e800" +slug: "test-post" +id: 0 + +# Timestamps are in ISO8601 UTC (`date -u +%Y-%m-%dT%H:%M:%SZ`) +created: "2026-01-14T01:25:14Z" +updated: "2026-01-14T01:25:14Z" +--- + +# h1 + +## h2 + +This is an example test post. + +## h2 + +content + +### h3 + +#### h4 + +456 diff --git a/svelte.config.js b/svelte.config.js index d57b4d7..7469c80 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,10 +1,13 @@ import adapter from '@sveltejs/adapter-static'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import importMap from './import_map.json' with { type: 'json' }; +import { mdsvex } from 'mdsvex'; /** @type {import('@sveltejs/kit').Config} */ const config = { - preprocess: vitePreprocess(), + preprocess: [mdsvex({ + extensions: ['.svx'], + }), vitePreprocess()], kit: { paths: { base: '/~mem', @@ -25,6 +28,7 @@ const config = { .filter((v) => v.length !== 0), ), }, + extensions: ['.svelte', '.svx'], }; export default config; |