Add blog post system to load from markdown files with mdsvex and marked

This commit is contained in:
Lewin Kelly 2023-07-07 07:34:54 +01:00
parent 71e6fb0fab
commit 53313ea02c
No known key found for this signature in database
GPG Key ID: C103AD9C84014FD7
17 changed files with 275 additions and 13 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
/pagesjson

View File

@ -4,18 +4,22 @@
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"build": "node pages/pages.js && vite build",
"preview": "vite preview",
"buildview": "vite build && vite preview",
"buildview": "node pages/pages.js && vite build && vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"md": "node pages/pages.js",
"lint": "prettier --plugin-search-dir . --check .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.1.0",
"@sveltejs/kit": "^1.21.0",
"@types/marked": "^5.0.0",
"@unocss/transformer-directives": "^0.53.4",
"marked": "^5.1.0",
"mdsvex": "^0.11.0",
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.1",
"sass": "^1.63.6",

14
pages/blog/first.md Normal file
View File

@ -0,0 +1,14 @@
First blog post
2023-06-07
I'm baby portland cred tote bag ethical glossier etsy fixie edison bulb retro irony. Helvetica beard humblebrag before they sold out photo booth yr cloud bread iceland ennui yes plz cold-pressed solarpunk tacos marxism. Yr occupy squid pug helvetica crucifix enamel pin subway tile bruh jean shorts fanny pack. Meditation gluten-free butcher PBR&B twee. Hammock selfies asymmetrical fixie before they sold out.
Grailed iceland austin chicharrones sriracha 8-bit praxis kinfolk blog everyday carry trust fund DIY pour-over. Sriracha disrupt PBR&B fam gorpcore bodega boys adaptogen butcher. Master cleanse tumeric slow-carb activated charcoal jean shorts freegan artisan poke trust fund poutine paleo marxism viral sartorial. Wayfarers neutral milk hotel unicorn art party skateboard. Actually williamsburg chicharrones palo santo direct trade seitan kickstarter humblebrag church-key air plant tacos sriracha. Vape blackbird spyplane kickstarter +1 hexagon PBR&B. Organic copper mug aesthetic, XOXO marxism quinoa subway tile irony lumbersexual authentic disrupt kitsch solarpunk.
Whatever JOMO organic artisan photo booth marfa, wayfarers yes plz cray. Keffiyeh gentrify thundercats affogato small batch retro you probably haven't heard of them drinking vinegar try-hard vibecession enamel pin. Tbh crucifix seitan, ennui jawn vice lo-fi DSA franzen fingerstache chillwave vape. Sartorial subway tile forage vaporware organic, XOXO letterpress.
Mustache bicycle rights copper mug pitchfork af typewriter. Vinyl copper mug bitters sus brunch. Biodiesel copper mug vexillologist, butcher asymmetrical seitan man bun everyday carry. Cray humblebrag lumbersexual Brooklyn chambray vice. Taxidermy viral keytar XOXO hell of intelligentsia next level.
Squid polaroid cold-pressed bitters, tousled enamel pin succulents. Seitan semiotics tumblr shabby chic heirloom salvia, beard gorpcore narwhal williamsburg forage. Austin synth locavore XOXO succulents artisan. Bodega boys bespoke bicycle rights shaman, mukbang leggings selvage irony yuccie polaroid kale chips activated charcoal chambray. Deep v migas pour-over edison bulb tilde chia vinyl, letterpress umami wolf hot chicken franzen taxidermy health goth tonx. Post-ironic gastropub heirloom, plaid literally dreamcatcher pop-up YOLO migas shoreditch brunch. Lo-fi glossier single-origin coffee, tattooed vegan hexagon kinfolk actually YOLO prism.
Dummy text? More like dummy thicc text, amirite?

14
pages/blog/second.md Normal file
View File

@ -0,0 +1,14 @@
Second blog post
2023-06-07
I'm baby portland cred tote bag ethical glossier etsy fixie edison bulb retro irony. Helvetica beard humblebrag before they sold out photo booth yr cloud bread iceland ennui yes plz cold-pressed solarpunk tacos marxism. Yr occupy squid pug helvetica crucifix enamel pin subway tile bruh jean shorts fanny pack. Meditation gluten-free butcher PBR&B twee. Hammock selfies asymmetrical fixie before they sold out.
Grailed iceland austin chicharrones sriracha 8-bit praxis kinfolk blog everyday carry trust fund DIY pour-over. Sriracha disrupt PBR&B fam gorpcore bodega boys adaptogen butcher. Master cleanse tumeric slow-carb activated charcoal jean shorts freegan artisan poke trust fund poutine paleo marxism viral sartorial. Wayfarers neutral milk hotel unicorn art party skateboard. Actually williamsburg chicharrones palo santo direct trade seitan kickstarter humblebrag church-key air plant tacos sriracha. Vape blackbird spyplane kickstarter +1 hexagon PBR&B. Organic copper mug aesthetic, XOXO marxism quinoa subway tile irony lumbersexual authentic disrupt kitsch solarpunk.
Whatever JOMO organic artisan photo booth marfa, wayfarers yes plz cray. Keffiyeh gentrify thundercats affogato small batch retro you probably haven't heard of them drinking vinegar try-hard vibecession enamel pin. Tbh crucifix seitan, ennui jawn vice lo-fi DSA franzen fingerstache chillwave vape. Sartorial subway tile forage vaporware organic, XOXO letterpress.
Mustache bicycle rights copper mug pitchfork af typewriter. Vinyl copper mug bitters sus brunch. Biodiesel copper mug vexillologist, butcher asymmetrical seitan man bun everyday carry. Cray humblebrag lumbersexual Brooklyn chambray vice. Taxidermy viral keytar XOXO hell of intelligentsia next level.
Squid polaroid cold-pressed bitters, tousled enamel pin succulents. Seitan semiotics tumblr shabby chic heirloom salvia, beard gorpcore narwhal williamsburg forage. Austin synth locavore XOXO succulents artisan. Bodega boys bespoke bicycle rights shaman, mukbang leggings selvage irony yuccie polaroid kale chips activated charcoal chambray. Deep v migas pour-over edison bulb tilde chia vinyl, letterpress umami wolf hot chicken franzen taxidermy health goth tonx. Post-ironic gastropub heirloom, plaid literally dreamcatcher pop-up YOLO migas shoreditch brunch. Lo-fi glossier single-origin coffee, tattooed vegan hexagon kinfolk actually YOLO prism.
Dummy text? More like dummy thicc text, amirite?

62
pages/pages.js Normal file
View File

@ -0,0 +1,62 @@
import { marked } from "marked"
import fs from "fs"
// Convert all markdown files in the pages directory and all subdirectories
// to HTML, and output as JSON files in the pagesjson directory.
function walk(dir) {
let results = []
for (const file of fs.readdirSync(dir)) {
const name = `${dir}/${file}`
const stat = fs.statSync(name)
if (stat && stat.isDirectory())
// Recurse into a subdirectory
results = results.concat(walk(name))
else if (file.endsWith(".md"))
// Is a file
results.push({ name })
}
return results
}
const allMdFiles = walk("./pages")
fs.rmSync("./pagesjson", { recursive: true })
allMdFiles.forEach(file => {
let md = fs.readFileSync(file.name, "utf8")
const lines = md.split("\n")
// Remove the first line of the file
const title = lines.shift()
const date = new Date(lines.shift())
lines.shift() // Remove the empty line
md = lines.join("\n")
const html = marked.parse(md, {
mangle: false,
headerIds: false,
})
const obj = { title, date, html }
fs.mkdirSync(
file.name
.replace("/pages/", "/pagesjson/")
.replace(file.name.split("/").pop(), ""),
{ recursive: true }
)
console.log(`Writing ${file.name.replace(".md", ".json")}`)
fs.writeFileSync(
file.name.replace("/pages/", "/pagesjson/").replace(".md", ".json"),
JSON.stringify(obj)
)
})
console.log("~ Done! ~")

View File

@ -19,9 +19,18 @@ devDependencies:
'@sveltejs/kit':
specifier: ^1.21.0
version: 1.21.0(svelte@4.0.3)(vite@4.3.9)
'@types/marked':
specifier: ^5.0.0
version: 5.0.0
'@unocss/transformer-directives':
specifier: ^0.53.4
version: 0.53.4
marked:
specifier: ^5.1.0
version: 5.1.0
mdsvex:
specifier: ^0.11.0
version: 0.11.0(svelte@4.0.3)
prettier:
specifier: ^2.8.8
version: 2.8.8
@ -413,10 +422,18 @@ packages:
/@types/estree@1.0.1:
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
/@types/marked@5.0.0:
resolution: {integrity: sha512-YcZe50jhltsCq7rc9MNZC/4QB/OnA2Pd6hrOSTOFajtabN+38slqgDDCeE/0F83SjkKBQcsZUj7VLWR0H5cKRA==}
dev: true
/@types/pug@2.0.6:
resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
dev: true
/@types/unist@2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: true
/@unocss/astro@0.53.4(vite@4.3.9):
resolution: {integrity: sha512-fR1F0mNktoN79R+t4GD4y3cvfHUVxtV0+9/6vraZTw3SOXTOMdHeisdxDLjJb3N1yer7XoKX+2GHrKCt873IUA==}
dependencies:
@ -1025,9 +1042,27 @@ packages:
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
/marked@5.1.0:
resolution: {integrity: sha512-z3/nBe7aTI8JDszlYLk7dDVNpngjw0o1ZJtrA9kIfkkHcIF+xH7mO23aISl4WxP83elU+MFROgahqdpd05lMEQ==}
engines: {node: '>= 18'}
hasBin: true
dev: true
/mdn-data@2.0.30:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
/mdsvex@0.11.0(svelte@4.0.3):
resolution: {integrity: sha512-gJF1s0N2nCmdxcKn8HDn0LKrN8poStqAicp6bBcsKFd/zkUBGLP5e7vnxu+g0pjBbDFOscUyI1mtHz+YK2TCDw==}
peerDependencies:
svelte: '>=3 <5'
dependencies:
'@types/unist': 2.0.6
prism-svelte: 0.4.7
prismjs: 1.29.0
svelte: 4.0.3
vfile-message: 2.0.4
dev: true
/merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: false
@ -1212,6 +1247,15 @@ packages:
hasBin: true
dev: true
/prism-svelte@0.4.7:
resolution: {integrity: sha512-yABh19CYbM24V7aS7TuPYRNMqthxwbvx6FF/Rw920YbyBWO3tnyPIqRMgHuSVsLmuHkkBS1Akyof463FVdkeDQ==}
dev: true
/prismjs@1.29.0:
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
engines: {node: '>=6'}
dev: true
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@ -1495,6 +1539,12 @@ packages:
busboy: 1.6.0
dev: true
/unist-util-stringify-position@2.0.3:
resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==}
dependencies:
'@types/unist': 2.0.6
dev: true
/unocss@0.53.4(postcss@8.4.24)(vite@4.3.9):
resolution: {integrity: sha512-UUEi+oh1rngHHP0DVESRS+ScoKMRF8q6GIQrElHb67gqG7GDEGpy3oocIA/6+1t71I4FFvnnxLMGIo9qAD0TEw==}
engines: {node: '>=14'}
@ -1531,6 +1581,13 @@ packages:
- vite
dev: false
/vfile-message@2.0.4:
resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==}
dependencies:
'@types/unist': 2.0.6
unist-util-stringify-position: 2.0.3
dev: true
/vite@4.3.9(sass@1.63.6):
resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==}
engines: {node: ^14.18.0 || >=16.0.0}

View File

@ -1,4 +1,4 @@
<div class="bg-#282726 px-6">
<div class="bg-#282625 px-6">
<span class="inline-block pt-3 pb-3">
<a href="/" class="text-light hover:text-#aaa text-xl font-light mb-5">
Revival Archive
@ -8,6 +8,7 @@
<nav class="inline-block ps-4">
<a class="navlink" href="/">Home</a>
<a class="navlink" href="/about">About</a>
<a class="navlink" href="/blog">Blog</a>
</nav>
</span>
</div>

10
src/routes/+error.svelte Normal file
View File

@ -0,0 +1,10 @@
<script lang="ts">
import { page } from "$app/stores"
</script>
<div class="flex flex-col justify-center items-center h-72vh">
<div class="bg-#1f1c1d p-8 px-22 rounded-4 light-text text-center">
<h1 class="light-text m-0 mb-5 text-2.2rem">Error {$page.status}</h1>
{$page.error?.message}
</div>
</div>

View File

@ -7,7 +7,7 @@
<Navbar />
<main
class="box-border px-5 sm:mx-auto w-full
class="box-border px-5 pt-12 sm:mx-auto w-full
sm:w-140 md:w-180 lg:w-220 xl:w-270 2xl:w-300">
<slot />
</main>

8
src/routes/+page.md Normal file
View File

@ -0,0 +1,8 @@
<svelte:head>
<title>Revival Archive</title>
</svelte:head>
# Revival Archive
Visit [kit.svelte.dev](https://kit.svelte.dev)
to read the documentation

View File

@ -1,9 +0,0 @@
<svelte:head>
<title>Revival Archive</title>
</svelte:head>
<h1>Welcome to SvelteKit</h1>
<p>
Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a>
to read the documentation
</p>

View File

@ -0,0 +1,26 @@
<script lang="ts">
export let data
</script>
<svelte:head>
<title>Blog • Revival Archive</title>
</svelte:head>
<h1>Posts</h1>
{#each data.posts as post}
<a href="/blog/{post.path}" class="text-white hover:text-#aaa">
<article class="bg-#1f1c1d py-3 px-6 rounded-5 mb-4">
<h2 class="my-2">{post.title}</h2>
<p class="my-2">
Published {new Date(post.date).toLocaleDateString("en-GB", {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
})}
</p>
</article>
</a>
{/each}

18
src/routes/blog/+page.ts Normal file
View File

@ -0,0 +1,18 @@
export async function load() {
const allPostFiles = import.meta.glob("../../../pagesjson/blog/*.json")
return {
posts: Promise.all(
Object.entries(allPostFiles).map(async ([path, resolver]) => {
const { title, date, html } = await resolver()
return {
title,
date,
html,
path: path.match(/(\w+)\.json/)[1],
}
})
),
}
}

View File

@ -0,0 +1,16 @@
import { error } from "@sveltejs/kit"
export async function load({ params }) {
try {
const { title, date, html } = await import(
`../../../../pagesjson/blog/${params.page}.json`
)
return {
html,
title,
date,
}
} catch (e) {
throw error(404, "Post not found")
}
}

View File

@ -0,0 +1,22 @@
<script lang="ts">
export let data
</script>
<article class="w-full sm:w-140 mx-auto">
<span class="flex my-10">
<h1 class="m-0">{data.title}</h1>
<span class="mt-auto ms-3">
Published {new Date(data.date).toLocaleDateString("en-GB", {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
})}
</span>
</span>
<div class="text-justify">
{@html data.html}
</div>
</article>

View File

@ -1,9 +1,12 @@
import adapter from "@sveltejs/adapter-auto"
import { vitePreprocess } from "@sveltejs/kit/vite"
import autoImport from "sveltekit-autoimport"
import { mdsvex } from "mdsvex"
/** @type {import('@sveltejs/kit').Config} */
export default {
extensions: [".svelte", ".svelte.md", ".md", ".svx"],
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: [
@ -12,6 +15,16 @@ export default {
components: ["./src/lib/components"],
flat: true,
}),
mdsvex({
extensions: [".svelte.md", ".md", ".svx"],
smartypants: {
dashes: "oldschool",
},
remarkPlugins: [],
rehypePlugins: [],
}),
],
kit: {

View File

@ -10,4 +10,9 @@ export default defineConfig({
}),
sveltekit(),
],
server: {
fs: {
allow: ["./pagesjson"],
},
},
})