summary refs log tree commit diff
diff options
context:
space:
mode:
authorAshelyn Rose <git@tempest.dev>2023-05-08 19:25:46 -0600
committerAshelyn Rose <git@tempest.dev>2023-05-08 19:29:19 -0600
commitd89d92d3936683f4212186cef517c7930dd5b33a (patch)
treecba24caddd1dc5f950b5e42eb333261f0c13dca5
parent6cddfdf8fe9bccc291ee8625d42cb42fd4ce2134 (diff)
add markdown rendering, copy in old posts
-rw-r--r--app/contact/page.tsx12
-rw-r--r--app/page.tsx2
-rw-r--r--app/posts/[slug]/page.tsx28
-rw-r--r--app/posts/page.tsx21
-rw-r--r--package-lock.json58
-rw-r--r--package.json2
-rw-r--r--posts/2022-08-01_thoughts-on-neovim.md65
-rw-r--r--posts/2023-01-04_advent-of-wasm.md75
-rw-r--r--styles/layout.css13
-rw-r--r--utils/post.ts56
10 files changed, 319 insertions, 13 deletions
diff --git a/app/contact/page.tsx b/app/contact/page.tsx
index 1ed2eb8..e596fb7 100644
--- a/app/contact/page.tsx
+++ b/app/contact/page.tsx
@@ -10,18 +10,18 @@ export default function Contact() {
   const [submitting, setSubmitting] = useState(false)
   const [status, setStatus] = useState('')
 
-  const nameRef = useRef()
-  const emailRef = useRef()
-  const messageRef = useRef()
+  const nameRef = useRef<HTMLInputElement>()
+  const emailRef = useRef<HTMLInputElement>()
+  const messageRef = useRef<HTMLTextAreaElement>()
 
   const submit = async (ev: FormEvent<HTMLFormElement>) => {
     ev.preventDefault()
 
     setStatus('')
 
-    const name: string = nameRef.current?.value ?? ''
-    const email: string = emailRef.current?.value ?? ''
-    const message: string = messageRef.current?.value ?? ''
+    const name = nameRef.current.value
+    const email = emailRef.current.value
+    const message = messageRef.current.value
 
     if (!name) setStatus(s => s + ' Name required.')
     if (!email) setStatus(s => s + ' Email required.')
diff --git a/app/page.tsx b/app/page.tsx
index bc84594..ce8241b 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -39,7 +39,7 @@ export default function Index() {
 
       <p>
         <em>Note:</em> This is the information for our system in aggregate,
-        for individual info see our <a href="/system">about</a> page
+        for individual info see our <a href="/about">about</a> page
       </p>
     </main>
   )
diff --git a/app/posts/[slug]/page.tsx b/app/posts/[slug]/page.tsx
new file mode 100644
index 0000000..2110a35
--- /dev/null
+++ b/app/posts/[slug]/page.tsx
@@ -0,0 +1,28 @@
+import { notFound } from 'next/navigation'
+import Markdown from 'markdown-to-jsx'
+
+import InfoBar from '~/components/InfoBar'
+import { getPostSlugs, loadSinglePage } from '~/utils/post'
+
+export async function generateStaticParams() {
+  const slugs = await getPostSlugs()
+  return slugs.map((slug: string) => ({ slug }))
+}
+
+export default async function Post({ params: { slug } }) {
+  const post = await loadSinglePage(slug)
+  if (!post) notFound()
+
+  return (
+    <>
+      <h1 className="pageTitle">
+        {post.title}
+      </h1>
+      <main className="mainColumn">
+        <InfoBar authorName={post.author} publishedDate={post.date} />
+        <Markdown>{post.body}</Markdown>
+      </main>
+    </>
+  )
+}
+
diff --git a/app/posts/page.tsx b/app/posts/page.tsx
index 34f0e7f..e713259 100644
--- a/app/posts/page.tsx
+++ b/app/posts/page.tsx
@@ -1,11 +1,26 @@
-export default function Posts() {
+import InfoBar from "~/components/InfoBar"
+import { Post, getPostSlugs, loadSinglePage } from "~/utils/post"
+
+export default async function Posts() {
+  const slugs = await getPostSlugs()
+  const posts = await Promise.all(slugs.map(loadSinglePage))
+
+  const sortedPosts = posts.sort((a: Post, b: Post) => b.date.valueOf() - a.date.valueOf())
+
   return (
     <>
       <h1 className="pageTitle">
         Posts
       </h1>
-      <main className="mainColumn">
-        <p>This will have posts here eventually we promise</p>
+      <main>
+        {sortedPosts.map((post: Post) => (
+          <div className="mainColumn">
+            <InfoBar authorName={post.author} publishedDate={post.date} />
+            <h2>{post.title}</h2>
+            <p>{post.subtitle}</p>
+            <a href={`/posts/${post.slug}`}>Read Post =&gt;</a>
+          </div>
+        ))}
       </main>
     </>
   )
diff --git a/package-lock.json b/package-lock.json
index 1223690..0bbb9a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,8 @@
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {
+        "front-matter": "^4.0.2",
+        "markdown-to-jsx": "^7.2.0",
         "next": "^13.4.1",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
@@ -224,6 +226,14 @@
       "resolved": "https://registry.npmjs.org/animejs/-/animejs-3.2.1.tgz",
       "integrity": "sha512-sWno3ugFryK5nhiDm/2BKeFCpZv7vzerWUcUPyAZLDhMek3+S/p418ldZJbJXo5ZUOpfm2kP2XRO4NJcULMy9A=="
     },
+    "node_modules/argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "dependencies": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
     "node_modules/async-validator": {
       "version": "1.8.5",
       "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.8.5.tgz",
@@ -469,6 +479,18 @@
         "once": "^1.4.0"
       }
     },
+    "node_modules/esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+      "bin": {
+        "esparse": "bin/esparse.js",
+        "esvalidate": "bin/esvalidate.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/expand-template": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -477,6 +499,14 @@
         "node": ">=6"
       }
     },
+    "node_modules/front-matter": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz",
+      "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==",
+      "dependencies": {
+        "js-yaml": "^3.13.1"
+      }
+    },
     "node_modules/fs-constants": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -531,6 +561,18 @@
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
       "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
     },
+    "node_modules/js-yaml": {
+      "version": "3.14.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+      "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+      "dependencies": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      },
+      "bin": {
+        "js-yaml": "bin/js-yaml.js"
+      }
+    },
     "node_modules/loose-envify": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -553,6 +595,17 @@
         "node": ">=10"
       }
     },
+    "node_modules/markdown-to-jsx": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.2.0.tgz",
+      "integrity": "sha512-3l4/Bigjm4bEqjCR6Xr+d4DtM1X6vvtGsMGSjJYyep8RjjIvcWtrXBS8Wbfe1/P+atKNMccpsraESIaWVplzVg==",
+      "engines": {
+        "node": ">= 10"
+      },
+      "peerDependencies": {
+        "react": ">= 0.14.0"
+      }
+    },
     "node_modules/mimic-response": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
@@ -933,6 +986,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
+    },
     "node_modules/streamsearch": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
diff --git a/package.json b/package.json
index 34b4561..4a8d60e 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,8 @@
   "author": "",
   "license": "ISC",
   "dependencies": {
+    "front-matter": "^4.0.2",
+    "markdown-to-jsx": "^7.2.0",
     "next": "^13.4.1",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
diff --git a/posts/2022-08-01_thoughts-on-neovim.md b/posts/2022-08-01_thoughts-on-neovim.md
new file mode 100644
index 0000000..13d5c33
--- /dev/null
+++ b/posts/2022-08-01_thoughts-on-neovim.md
@@ -0,0 +1,65 @@
+---
+title: Thoughts on Neovim
+subtitle: Who even needs an IDE anyways?
+author: rose
+---
+
+## Why I'm using Neovim
+
+When I first started coding in high school and then later in early
+college I used to jump around between editors a lot more than I do today.
+I used Notepad++, then Visual Studio, briefly Netbeans, then Atom.
+
+But since settling into frontend web development I've stayed with VSCode
+for a very long time.  I liked it because it was straightforward to get
+started with, but versatile enough to extend for other languages.
+Between various jobs and projects I used it for Javascript, Java, C#,
+Rust, and C - and it did admirably at pretty much all of these.
+
+But about a year ago I saw that VSCode had a Neovim plugin, and I was
+intrigued.  I'd wanted to get more familiar with Vim beyond the basic
+hjkl navigation, and this seemed like a great way to do that! 
+
+So for the last year and change I've had the
+<a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=asvetliakov.vscode-neovim">vscode-neovim</a> plugin
+plugin installed, and I've been really enjoying it!
+
+I quickly fell in love with visual block mode, or the "delete N words"
+commands.  They're just so handy I suddenly felt like they were missing
+if I needed to edit code any other way!
+
+But over the weekend I made the jump from using Neovim inside VSCode to
+using it more or less on its own.  I saw a video that mentioned the
+AstroNvim configuration framework and Neovide, and decided "yeah, I think
+I want to try that", and a few days later . . . here we are.
+
+## How is it going?
+
+Overall, surprisingly well.
+
+The AstroNvim config I'm using already had NeoTree set up which is
+very nice.  I've figured out how to get ESLint and Prettier configured
+for work, rust-analyzer installed for my own projects, I've been poking
+at themes over and over again, and honestly . . . I'm really liking this.
+
+Getting Neovide to connect to a VM over the network was relatively
+straightforward, I love how easy it is to drop my config into git and
+keep it synced between computers, and finally having proper mouse support
+(which I never could get sorted out with my terminal) is a pretty big
+game changer for when I'm just reading code.
+
+Also, I'd be lying if I said that I didn't love the smooth scrolling and
+cursor animation.  I am a simple girl after all.
+
+## Should you try replacing your IDE?
+
+That is a tricky question to answer.
+
+I was comfortable spending some time experimenting with this because I
+already had decent familiarity with Vim and had been using Neovim
+specifically for a while.  If you don't have any similar experience,
+the learning curve is going to be pretty steep.
+
+But hey - if you're looking for a challenge, you'll definitely learn
+a lot.
+
diff --git a/posts/2023-01-04_advent-of-wasm.md b/posts/2023-01-04_advent-of-wasm.md
new file mode 100644
index 0000000..12a48c8
--- /dev/null
+++ b/posts/2023-01-04_advent-of-wasm.md
@@ -0,0 +1,75 @@
+---
+title: Advent of Wasm
+subtitle: Now with 87% more pain
+author: rose
+---
+
+So the last few years I have done Advent of Code off and on.  Sometimes
+I have tried to learn a new language, other times I was just trying to
+beat my dad each evening.  This year though, this year I don't know what
+I was thinking.
+
+It was several weeks after everyone else had started, I had largely written
+it off for the year - I was not up for it.  Until a terrible idea crossed
+my mind.
+
+Like an intrusive thought, my mind asked: "Well you've been wanting to do
+something in web assembly for a while right?  How bad could it be?"
+
+<br/>
+
+Turns out I was definitely not ready for this.
+
+## So what was so hard about it?
+
+More than anything else, I forgot how much you need to do by hand to do
+any sort of assembly.  The first day saw me spending several hours just
+on some loader code to pass the puzzle input in from JS, call a wasm
+function, and then read back the result.
+
+Next was a few functions for reading numbers out of the wasm memory buffer,
+parsing them from ascii, etc.  The core read loop was not too tricky, but
+the bit that took far longer than it had any reason to was converting my
+answer back to ascii and shoving it into an output area.
+
+Really none of it was surprising, and none of it <em><strong>*should*</strong></em> have
+been that hard ... it's just been a while since this Javascript girl
+has written truly low-level code.
+
+To make matters worse I got hard-core distracted by the non-wasm part of
+my wasm project.  After the first day I returned to my stub JS loader and
+expanded it into a little wasm explorer.
+
+I added a code view, syntax highlighting, auto-loaded my puzzle inputs,
+even made a janky little dynamic list that would automatically pick up
+new days' solutions as I added them to the repo without needing me to
+touch the loader page each day.
+
+In the end I'm really quite proud of it, I will absolutely be reusing
+this setup for future years, and you should
+<a href="https://aoc2022.tempest.dev/" target="_blank">check it out</a>
+if you haven't already ... but for wanting to challenge myself with
+something new I was doing a lot of the same-old.
+
+Ultimately I got through 3 days before giving up jusst because every
+step along the way involved <em><strong>*so much*</strong></em> extra
+code.  I may come back to some of the puzzles later, but for now I'm
+kind of happy with what I did, and I don't feel like I need to prove
+myself by doing more.  I was doing it for fun, and so I stopped when
+it stopped being fun.
+
+## Tips if you want to get into writing wasm by hand?
+
+Uhh ... maybe consider don't?
+
+Jokes aside: do a throwaway project or two so you get used to passing
+data into and out of wasm, whatever parsing you're going to do, etc.
+
+Do everything in your power to make sure you can focus on the actual
+wasm part of your project, because (at least if you're anything like
+me) it's easy to get sidetracked with all that.
+
+With that said: I had fun.  Doing new things is always a treat, so if
+you're looking for something new to try definitely consider giving
+webassembly a look.
+
diff --git a/styles/layout.css b/styles/layout.css
index 4a64adb..6c8578d 100644
--- a/styles/layout.css
+++ b/styles/layout.css
@@ -33,7 +33,7 @@ header {
   padding: var(--text-padding);
   padding-bottom: 0;
   box-sizing: border-box;
-  align-items: start;
+  align-items: flex-start;
   justify-items: flex-start;
 }
 
@@ -125,12 +125,15 @@ header:not(.homepage) ~ h1.pageTitle {
 }
 
 main.mainColumn {
+  min-height: calc(var(--header-overlap) + var(--min-main-overhang));
+}
+
+.mainColumn {
   width: var(--main-width);
   box-sizing: border-box;
   padding: var(--text-padding);
   margin: 0 auto;
   background: var(--main-background);
-  min-height: calc(var(--header-overlap) + var(--min-main-overhang));
   box-shadow: 0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12);
 }
 
@@ -138,13 +141,17 @@ header.homepage ~ .mainColumn {
   min-height: calc(var(--header-overlap) + var(--min-main-overhang) - var(--header-bar-height));
 }
 
+.mainColumn ~ .mainColumn {
+  margin-top: calc(2 * var(--text-padding));
+}
+
 main.mainColumn > p:first-child {
   margin-top: 0;
 }
 
 footer {
   flex: 1;
-  align-items: end;
+  align-items: flex-end;
   box-sizing: border-box;
   width: var(--main-width);
   margin: 0 auto;
diff --git a/utils/post.ts b/utils/post.ts
new file mode 100644
index 0000000..b2dd328
--- /dev/null
+++ b/utils/post.ts
@@ -0,0 +1,56 @@
+import { promises as fs } from 'fs'
+import path from 'path'
+
+import frontmatter from 'front-matter'
+
+export interface Post {
+  slug: string,
+  date: Date,
+  title: string,
+  subtitle?: string,
+  author: string,
+  body: string
+}
+
+const POST_FILE_PATTERN = /^(?<date>[0-9]{4}-[0-9]{2}-[0-9]{2}(-[0-9]{1,2})?)_(?<slug>[^\.]+)\.md$/
+
+export async function getPostSlugs(): Promise<string[]> {
+  const postsDir = path.join(process.cwd(), 'posts')
+  const postPaths = await fs.readdir(postsDir)
+  return postPaths.map(getSlugFromFilePath).filter(slug => slug !== null)
+}
+
+export function getSlugFromFilePath(postPath: string): string | null {
+  const regexResult = POST_FILE_PATTERN.exec(postPath)
+  if (!regexResult) return null
+  return regexResult.groups.slug
+}
+
+export async function loadSinglePage(slug: string): Promise<Post | null> {
+  const postsDir = path.join(process.cwd(), 'posts')
+  const postPaths = await fs.readdir(postsDir)
+  const postMatch: RegExpExecArray | null = postPaths
+    .map((postFile: string) => POST_FILE_PATTERN.exec(postFile))
+    .filter((regexResult: RegExpExecArray | null) => regexResult !== null)
+    .find((regexResult: RegExpExecArray) => regexResult.groups.slug === slug)
+
+  if (!postMatch) return null;
+
+  const fileName = `${postMatch.groups.date}_${postMatch.groups.slug}.md`
+  const fileContents = (await fs.readFile(path.join(process.cwd(), 'posts', fileName))).toString('utf8')
+  const { attributes, body } = frontmatter(fileContents)
+
+  type Attributes = { [key: string]: string }
+  const data: Attributes = attributes as Attributes
+
+  if (!data.title) return null;
+
+  return {
+    slug: postMatch.groups.slug,
+    date: new Date(postMatch.groups.date),
+    title: data.title,
+    subtitle: data.subtitle,
+    author: data.author,
+    body
+  }
+}