f5767ea switch to dynamic snippet pages
diff --git a/src/backends/snippets.ts b/src/backends/snippets.ts
index f64f65e..c29c670 100644
--- a/src/backends/snippets.ts
+++ b/src/backends/snippets.ts
@@ -11,6 +11,14 @@ export type Snippet = {
publishedDate: Dayjs;
};
+type RawSnippet = {
+ title: string;
+ slug: string;
+ markdown: string;
+ tags: string[];
+ "pub-date": string;
+};
+
const host = process.env.SNIPPETS_HOST! ?? import.meta.env.SNIPPETS_HOST;
export async function getTags(): Promise<{ tag: string; count: number }[]> {
@@ -23,16 +31,19 @@ export async function getTags(): Promise<{ tag: string; count: number }[]> {
return data;
}
-export async function getSnippetById(id: string): Promise<Snippet[]> {
+export async function getSnippetBySlug(slug: string): Promise<Snippet> {
const params = new URLSearchParams();
- params.append("id", id);
+ params.append("slug", slug);
- const res = await fetch(`${host}/api/snippet?${params}`);
- const data = await res.json();
+ const res = await fetch(`${host}/api/snippet-by-slug?${params}`);
+ const data = (await res.json()) as RawSnippet;
if (res.status !== 200) {
throw new Error(`Failed to fetch snippets: ${res.status}`);
}
- return data;
+ return {
+ ...data,
+ publishedDate: dayjs(data["pub-date"]),
+ };
}
export async function getSnippetsByTag(
diff --git a/src/pages/tech/snippets/[slug].astro b/src/pages/tech/snippets/[slug].astro
index 6101673..1872555 100644
--- a/src/pages/tech/snippets/[slug].astro
+++ b/src/pages/tech/snippets/[slug].astro
@@ -1,32 +1,13 @@
---
import BasePage from '../../../layouts/BasePage.astro';
-import { GET_SNIPPETS_STEP, getSnippetById, getSnippets } from '../../../backends/snippets';
-import { marked } from "marked";
+import { getSnippetBySlug } from '../../../backends/snippets';
import Snippet from '../../../components/Snippet.astro';
-export async function getStaticPaths() {
- const snippets = [];
- let page = 1;
- while(true) {
- const newSnippets = await getSnippets(page);
- snippets.push(...newSnippets);
- if (newSnippets.length < GET_SNIPPETS_STEP) break;
- page++;
- }
- return snippets.map(snippet => ({
- params: { slug: snippet.slug },
- props: { snippet },
- }));
-}
-
-export const prerender = true;
-const { snippet } = Astro.props;
-
-// // TODO: Get snippets by tag
-// const snippets = await getSnippetById(tag.tag);
-// snippets.sort((a, b) => b.publishedDate.diff(a.publishedDate));
+const { slug } = Astro.params;
+const snippet = await getSnippetBySlug(slug!);
---
<BasePage>
<Snippet snippet={snippet} />
+ <a href="/tech/snippets">all snippets</a>
</BasePage>
diff --git a/src/pages/tech/snippets/index.astro b/src/pages/tech/snippets/index.astro
index 284574d..1ec4d28 100644
--- a/src/pages/tech/snippets/index.astro
+++ b/src/pages/tech/snippets/index.astro
@@ -1,13 +1,18 @@
---
import BasePage from "../../../layouts/BasePage.astro";
-import { getSnippets, getTags } from "../../../backends/snippets";
+import { GET_SNIPPETS_STEP, getSnippets, getTags } from "../../../backends/snippets";
import Snippet from "../../../components/Snippet.astro";
import Accordian from "../../../components/solid/Accordion";
+import { logger } from "../../../backends/logger";
const title = "Snippets";
const description = "Collection of my code snippets";
-const snippets = await getSnippets();
+
+const page = Number(Astro.url.searchParams.get("page") ?? "1");
+logger.info({params: Astro.params}, 'params');
+const snippets = await getSnippets(page);
+
let tags = await getTags();
tags = tags.sort((a, b) => b.count - a.count);
---
@@ -29,6 +34,10 @@ tags = tags.sort((a, b) => b.count - a.count);
<div>
{snippets.map(snippet => (<Snippet snippet={snippet} /> ))}
</div>
+
+ {snippets.length >= GET_SNIPPETS_STEP && (
+ <a href={`/tech/snippets?page=${page + 1}`}>next page</a>
+ )}
</main>
</BasePage>
diff --git a/src/pages/tech/snippets/tags/[tag].astro b/src/pages/tech/snippets/tags/[tag].astro
index adc129b..f668d58 100644
--- a/src/pages/tech/snippets/tags/[tag].astro
+++ b/src/pages/tech/snippets/tags/[tag].astro
@@ -1,26 +1,16 @@
---
-import { getSnippetsByTag, getTags } from '../../../../backends/snippets';
+import { getSnippetsByTag } from '../../../../backends/snippets';
import BasePage from '../../../../layouts/BasePage.astro';
+const { tag } = Astro.params;
-export async function getStaticPaths() {
- const tags = await getTags();
- return tags.map(tag => ({
- params: { tag: tag.tag },
- props: { tag },
- }));
-}
-
-export const prerender = true;
-const { tag } = Astro.props;
-
-// TODO: Get snippets by tag
-const snippets = await getSnippetsByTag(tag.tag);
+const snippets = await getSnippetsByTag(tag!);
snippets.sort((a, b) => b.publishedDate.diff(a.publishedDate));
---
<BasePage>
- <p>Snippets with tag {tag.tag}</p>
+ <p>Snippets with tag {tag}</p>
<ul>
{snippets.map(snippet => (<li><a href=`/tech/snippets/${snippet.slug}`>{snippet.title} {snippet.publishedDate.format('DD-MM-YY')}</a></li>))}
</ul>
+ <a href="/tech/snippets">all snippets</a>
</BasePage>
b73b02d add snippet section under tech
diff --git a/package.json b/package.json
index 2ccd42f..9fd4f1f 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"htmx.org": "^1.9.12",
"jose": "^5.6.2",
"marked": "^13.0.1",
+ "pino": "^9.7.0",
"pocketbase": "^0.21.3",
"solid-js": "^1.8.17",
"typescript": "^5.4.5",
diff --git a/src/backends/logger.ts b/src/backends/logger.ts
new file mode 100644
index 0000000..ed26420
--- /dev/null
+++ b/src/backends/logger.ts
@@ -0,0 +1,3 @@
+import pino from "pino";
+
+export const logger = pino();
diff --git a/src/backends/microBlog.ts b/src/backends/microBlog.ts
index 96c9b79..3c085e2 100644
--- a/src/backends/microBlog.ts
+++ b/src/backends/microBlog.ts
@@ -1,4 +1,6 @@
import PocketBase from "pocketbase";
+import type { AstroIntegration } from "astro";
+import { logger } from "./logger";
export type MicroBlogPostImage = {
id: string;
@@ -37,6 +39,7 @@ class MicroBlogBackend {
}
private async login() {
+ logger.info("Logging in to PocketBase");
const userName = "personal_site_astro";
const pw =
process.env.POCKET_BASE_PASSWORD! ?? import.meta.env.POCKET_BASE_PASSWORD;
diff --git a/src/backends/snippets.ts b/src/backends/snippets.ts
new file mode 100644
index 0000000..f64f65e
--- /dev/null
+++ b/src/backends/snippets.ts
@@ -0,0 +1,89 @@
+import { logger } from "./logger";
+import { dayjs } from "../utils/time";
+import type { Dayjs } from "dayjs";
+
+export type Snippet = {
+ // id: string;
+ title: string;
+ slug: string;
+ markdown: string;
+ tags: string[];
+ publishedDate: Dayjs;
+};
+
+const host = process.env.SNIPPETS_HOST! ?? import.meta.env.SNIPPETS_HOST;
+
+export async function getTags(): Promise<{ tag: string; count: number }[]> {
+ const res = await fetch(`${host}/api/tags`);
+ const data = await res.json();
+ if (res.status !== 200) {
+ throw new Error(`Failed to fetch tags: ${res.status}`);
+ }
+
+ return data;
+}
+
+export async function getSnippetById(id: string): Promise<Snippet[]> {
+ const params = new URLSearchParams();
+ params.append("id", id);
+
+ const res = await fetch(`${host}/api/snippet?${params}`);
+ const data = await res.json();
+ if (res.status !== 200) {
+ throw new Error(`Failed to fetch snippets: ${res.status}`);
+ }
+ return data;
+}
+
+export async function getSnippetsByTag(
+ tag: string
+): Promise<{ title: string; slug: string; publishedDate: Dayjs }[]> {
+ const params = new URLSearchParams();
+ params.append("tag", tag);
+
+ const res = await fetch(`${host}/api/tag?${params}`);
+ const data = (await res.json()) as {
+ title: string;
+ slug: string;
+ "pub-date": string;
+ }[];
+ if (res.status !== 200) {
+ throw new Error(`Failed to fetch snippets: ${res.status}`);
+ }
+
+ return data.map((snippet) => {
+ return {
+ title: snippet.title,
+ slug: snippet.slug,
+ publishedDate: dayjs(snippet["pub-date"]),
+ };
+ });
+}
+
+export const GET_SNIPPETS_STEP = 25;
+export async function getSnippets(page = 1): Promise<Snippet[]> {
+ logger.info({ page }, "Getting snippets");
+ const params = new URLSearchParams();
+ params.append("limit", "25");
+ params.append("skip", String((page - 1) * GET_SNIPPETS_STEP));
+
+ const res = await fetch(`${host}/api/snippets?${params}`);
+ const data = (await res.json()) as {
+ title: string;
+ "pub-date": string;
+ tags: string[];
+ slug: string;
+ markdown: string;
+ // id: string;
+ }[];
+ if (res.status !== 200) {
+ throw new Error(`Failed to fetch snippets: ${res.status}`);
+ }
+
+ return data.map((snippet) => {
+ return {
+ ...snippet,
+ publishedDate: dayjs(snippet["pub-date"]),
+ };
+ });
+}
diff --git a/src/components/Snippet.astro b/src/components/Snippet.astro
new file mode 100644
index 0000000..4e847b2
--- /dev/null
+++ b/src/components/Snippet.astro
@@ -0,0 +1,41 @@
+---
+import { marked } from "marked";
+import type { Snippet } from "../backends/snippets";
+interface Props {
+ snippet: Snippet;
+}
+
+const { snippet } = Astro.props;
+
+---
+<div class="snippet">
+ <h3 class="title">{snippet.title}</h3>
+ <div class="row">
+ <p class="title">{snippet.publishedDate.format("DD-MM-YY")}</p>
+ <a href={`/tech/snippets/${snippet.slug}`}>%</a>
+ </div>
+ <div set:html={marked.parse(snippet.markdown)} />
+ <p>- [ {snippet.tags.map(tag => (
+ <a class="tag" href={`/tech/snippets/tags/${tag}`}>{tag}</a>
+ ))}]
+ </p>
+</div>
+<style>
+.row {
+ display: flex;
+}
+.snippet {
+ .title {
+ margin: 0;
+ margin-right: 0.5rem;
+ display: inline-block;
+ }
+ .tag {
+ display: inline-block;
+ margin-right: 0.25rem;
+ }
+ padding: 1rem;
+ margin-bottom: 1rem;
+ border: 1px solid black;
+}
+</style>
diff --git a/src/components/solid/Accordion.tsx b/src/components/solid/Accordion.tsx
new file mode 100644
index 0000000..5cc5dc7
--- /dev/null
+++ b/src/components/solid/Accordion.tsx
@@ -0,0 +1,26 @@
+import { createSignal, type JSX } from "solid-js";
+
+function Accordion({ children }: { children: JSX.Element }) {
+ const [open, setOpen] = createSignal<boolean>(false);
+ return (
+ <>
+ <div
+ style={{
+ "max-height": open() ? "100%" : "3.75rem",
+ overflow: "hidden",
+ transition: "max-height 0.3s ease-in-out",
+ }}
+ classList={{ open: open() }}
+ >
+ {children}
+ </div>
+ {!open() && (
+ <button type="button" onClick={() => setOpen(true)}>
+ show more
+ </button>
+ )}
+ </>
+ );
+}
+
+export default Accordion;
diff --git a/src/pages/tech/index.astro b/src/pages/tech/index.astro
index 193b3a1..3b32097 100644
--- a/src/pages/tech/index.astro
+++ b/src/pages/tech/index.astro
@@ -8,7 +8,8 @@ import MicroBlogPost from "../../components/solid/MicroBlogPost";
import BasePage from "../../layouts/BasePage.astro";
import { authCookieName, verifyJWT } from "../../utils/auth";
-let { title, description } = Astro.params;
+const title = "Tech";
+const description = "A hub for my adventures in software and hardware.";
const posts = await getCollection('techBlog');
dayjs.extend(relativeTime)
@@ -28,10 +29,13 @@ if (jwt) {
<BasePage title={title} description={description}>
<main>
<div class="content">
- <slot />
-
<h1>Tech</h1>
<p>A hub for my adventures in software and hardware.</p>
+ <ul>
+ <li>
+ <a href="/tech/snippets">Code Snippets</a>
+ </li>
+ </ul>
<h2>Recent tech related micro-blog posts</h2>
{microBlogPosts.map((post) => <MicroBlogPost post={post} />)}
{loggedIn && (<a href="/micro-blog/edit-groups">Add more tech posts</a>)}
diff --git a/src/pages/tech/snippets/[slug].astro b/src/pages/tech/snippets/[slug].astro
new file mode 100644
index 0000000..6101673
--- /dev/null
+++ b/src/pages/tech/snippets/[slug].astro
@@ -0,0 +1,32 @@
+---
+import BasePage from '../../../layouts/BasePage.astro';
+import { GET_SNIPPETS_STEP, getSnippetById, getSnippets } from '../../../backends/snippets';
+import { marked } from "marked";
+import Snippet from '../../../components/Snippet.astro';
+
+export async function getStaticPaths() {
+ const snippets = [];
+ let page = 1;
+ while(true) {
+ const newSnippets = await getSnippets(page);
+ snippets.push(...newSnippets);
+ if (newSnippets.length < GET_SNIPPETS_STEP) break;
+ page++;
+ }
+ return snippets.map(snippet => ({
+ params: { slug: snippet.slug },
+ props: { snippet },
+ }));
+}
+
+export const prerender = true;
+const { snippet } = Astro.props;
+
+// // TODO: Get snippets by tag
+// const snippets = await getSnippetById(tag.tag);
+// snippets.sort((a, b) => b.publishedDate.diff(a.publishedDate));
+
+---
+<BasePage>
+ <Snippet snippet={snippet} />
+</BasePage>
diff --git a/src/pages/tech/snippets/index.astro b/src/pages/tech/snippets/index.astro
new file mode 100644
index 0000000..284574d
--- /dev/null
+++ b/src/pages/tech/snippets/index.astro
@@ -0,0 +1,45 @@
+---
+import BasePage from "../../../layouts/BasePage.astro";
+import { getSnippets, getTags } from "../../../backends/snippets";
+import Snippet from "../../../components/Snippet.astro";
+import Accordian from "../../../components/solid/Accordion";
+
+
+const title = "Snippets";
+const description = "Collection of my code snippets";
+const snippets = await getSnippets();
+let tags = await getTags();
+tags = tags.sort((a, b) => b.count - a.count);
+---
+
+<BasePage title={title} description={description}>
+ <main>
+ <div class="content">
+ <h1>Snippets</h1>
+ <p>Collection of my code snippets</p>
+ <p>Tags:</p>
+ <Accordian client:load>
+ <div class="tags">
+ {tags.map(tag => (
+ <a class="tag" href={`/tech/snippets/tags/${tag.tag}`}>{tag.tag} ({tag.count})</a>
+ ))}
+ </div>
+ </Accordian>
+ <hr />
+ <div>
+ {snippets.map(snippet => (<Snippet snippet={snippet} /> ))}
+ </div>
+ </main>
+</BasePage>
+
+<style>
+.tags {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1rem;
+
+ @media (max-width: 768px) {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+</style>
diff --git a/src/pages/tech/snippets/tags/[tag].astro b/src/pages/tech/snippets/tags/[tag].astro
new file mode 100644
index 0000000..adc129b
--- /dev/null
+++ b/src/pages/tech/snippets/tags/[tag].astro
@@ -0,0 +1,26 @@
+---
+import { getSnippetsByTag, getTags } from '../../../../backends/snippets';
+import BasePage from '../../../../layouts/BasePage.astro';
+
+export async function getStaticPaths() {
+ const tags = await getTags();
+ return tags.map(tag => ({
+ params: { tag: tag.tag },
+ props: { tag },
+ }));
+}
+
+export const prerender = true;
+const { tag } = Astro.props;
+
+// TODO: Get snippets by tag
+const snippets = await getSnippetsByTag(tag.tag);
+snippets.sort((a, b) => b.publishedDate.diff(a.publishedDate));
+
+---
+<BasePage>
+ <p>Snippets with tag {tag.tag}</p>
+ <ul>
+ {snippets.map(snippet => (<li><a href=`/tech/snippets/${snippet.slug}`>{snippet.title} {snippet.publishedDate.format('DD-MM-YY')}</a></li>))}
+ </ul>
+</BasePage>