aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatarLarge Libravatar memdmp <memdmpestrogenzone>2026-01-14 04:36:14 +0100
committerLibravatarLarge Libravatar memdmp <memdmpestrogenzone>2026-01-14 04:36:14 +0100
commit481c06cdb6bfb370ba2d84f4b6bb4ba260eb14cd (patch)
treeb022a161677183e9a08e2183c1a254a553907927
parent347824c75406238a6e9d50b4833a60c2c43140a7 (diff)
downloadmem-estrogen-zone-481c06cdb6bfb370ba2d84f4b6bb4ba260eb14cd.tar.gz
mem-estrogen-zone-481c06cdb6bfb370ba2d84f4b6bb4ba260eb14cd.tar.bz2
mem-estrogen-zone-481c06cdb6bfb370ba2d84f4b6bb4ba260eb14cd.tar.lz
mem-estrogen-zone-481c06cdb6bfb370ba2d84f4b6bb4ba260eb14cd.zip

feat: start work on blog

-rw-r--r--.vscode/settings.json5
-rw-r--r--src/app.css35
-rw-r--r--src/app.html1
-rw-r--r--src/lib/blog/Post.svelte19
-rw-r--r--src/lib/blog/Post.ts28
-rw-r--r--src/params/int.ts3
-rw-r--r--src/routes/blog/+page.server.ts13
-rw-r--r--src/routes/blog/+page.svelte13
-rw-r--r--src/routes/blog/[id=int]-[slug]/+page.server.ts2
-rw-r--r--src/routes/blog/[id=int]-[slug]/+page.svelte8
-rw-r--r--src/routes/blog/[id=int]-[slug]/+page.ts2
-rw-r--r--src/routes/blog/[id=int]/+page.server.ts12
-rw-r--r--src/routes/blog/[id=int]/+page.svelte28
-rw-r--r--src/routes/blog/[id=int]/+page.ts11
-rw-r--r--src/routes/blog/base-post.svx15
-rw-r--r--src/routes/blog/dynamic-posts.ts4
-rw-r--r--src/routes/blog/posts.ts6
-rw-r--r--src/routes/blog/posts/test-post.svx27
-rw-r--r--svelte.config.js6
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;