+
+
{{ post.title }}
+
+
+
+
-
-
-
-
+ class="aspect-[16/9] drop-shadow-2xl mt-20 w-full max-w-3xl mx-auto rounded bg-dark-100 object-cover sm:aspect-[2/1] lg:aspect-[3/2]" />
+
diff --git a/server/api/article.get.ts b/server/api/article.get.ts
new file mode 100644
index 0000000..ed01858
--- /dev/null
+++ b/server/api/article.get.ts
@@ -0,0 +1,22 @@
+import { join } from "node:path";
+import { usePost } from "~/composables/server/Post";
+
+export default defineEventHandler(async (event) => {
+ const url = new URL(
+ event.node.req.url ?? "",
+ `http://${event.node.req.headers.host}`,
+ );
+
+ // Get the path query parameter
+ const filePath = decodeURIComponent(url.searchParams.get("path") ?? "");
+
+ const post = usePost(filePath);
+
+ if (!post)
+ throw createError({
+ statusCode: 404,
+ message: "Post not found",
+ });
+
+ return post;
+});
diff --git a/server/api/articles.get.ts b/server/api/articles.get.ts
new file mode 100644
index 0000000..d6fdf88
--- /dev/null
+++ b/server/api/articles.get.ts
@@ -0,0 +1,7 @@
+import { usePostList } from "~/composables/server/Post";
+
+export default defineEventHandler(async () => {
+ const files = await usePostList();
+
+ return files ?? [];
+});
diff --git a/server/tsconfig.json b/server/tsconfig.json
new file mode 100644
index 0000000..deaaf79
--- /dev/null
+++ b/server/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../.nuxt/tsconfig.server.json"
+}
diff --git a/types.d.ts b/types.d.ts
new file mode 100644
index 0000000..2ab38b1
--- /dev/null
+++ b/types.d.ts
@@ -0,0 +1 @@
+declare module "@hackmd/markdown-it-task-lists";
diff --git a/types/posts.ts b/types/posts.ts
new file mode 100644
index 0000000..16bf64b
--- /dev/null
+++ b/types/posts.ts
@@ -0,0 +1,28 @@
+export interface FrontMatter {
+ title: string;
+ description: string;
+ image: string;
+ created_at: string;
+ private?: string;
+ author: string;
+ author_image: string;
+ author_handle: string;
+}
+
+export interface Post {
+ title: string;
+ description: string;
+ image: string;
+ banner?: string;
+ author: Author;
+ private: boolean;
+ created_at: string;
+ content: string;
+ path: string;
+}
+
+export interface Author {
+ name: string;
+ image: string;
+ handle: string;
+}
diff --git a/types/types.ts b/types/types.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/utils/markdown.ts b/utils/markdown.ts
new file mode 100644
index 0000000..57a10fd
--- /dev/null
+++ b/utils/markdown.ts
@@ -0,0 +1,140 @@
+import markdownItTaskLists from "@hackmd/markdown-it-task-lists";
+import { fromHighlighter } from "@shikijs/markdown-it/core";
+import MarkdownIt from "markdown-it";
+import markdownItAnchor from "markdown-it-anchor";
+import markdownItContainer from "markdown-it-container";
+import markdownItTocDoneRight from "markdown-it-toc-done-right";
+import { createHighlighterCore } from "shiki/core";
+
+const highlighter = createHighlighterCore({
+ themes: [import("shiki/themes/rose-pine.mjs")],
+ langs: [
+ import("shiki/langs/javascript.mjs"),
+ import("shiki/langs/typescript.mjs"),
+ import("shiki/langs/python.mjs"),
+ import("shiki/langs/toml.mjs"),
+ import("shiki/langs/rust.mjs"),
+ import("shiki/langs/sql.mjs"),
+ import("shiki/langs/json.mjs"),
+ import("shiki/langs/html.mjs"),
+ import("shiki/langs/css.mjs"),
+ import("shiki/langs/scss.mjs"),
+ import("shiki/langs/bash.mjs"),
+ import("shiki/langs/shell.mjs"),
+ import("shiki/langs/yaml.mjs"),
+ import("shiki/langs/markdown.mjs"),
+ ],
+ loadWasm: import("shiki/wasm"),
+});
+
+export const getMarkdownRenderer = async () => {
+ const renderer = MarkdownIt({
+ html: true,
+ linkify: true,
+ });
+
+ const otherRenderer = MarkdownIt({
+ html: true,
+ linkify: true,
+ });
+
+ for (const ren of [renderer, otherRenderer]) {
+ ren.use(
+ // @ts-ignore
+ fromHighlighter(await highlighter, {
+ theme: "rose-pine",
+ }),
+ );
+
+ ren.use(markdownItAnchor, {
+ permalink: markdownItAnchor.permalink.ariaHidden({
+ symbol: "",
+ placement: "before",
+ }),
+ });
+
+ ren.use(markdownItTocDoneRight, {
+ containerClass: "toc",
+ level: [1, 2, 3, 4],
+ listType: "ul",
+ listClass: "toc-list",
+ itemClass: "toc-item",
+ linkClass: "toc-link",
+ });
+
+ ren.use(markdownItTaskLists);
+
+ ren.use(markdownItContainer, "spoiler");
+ }
+
+ renderer.use((md) => {
+ md.renderer.rules.html_block = (tokens, idx) => {
+ // Modify figure tags
+ if (tokens[idx].content.startsWith("