add tags page and dockerfile lol
This commit is contained in:
13
web/Dockerfile
Normal file
13
web/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FROM node:24-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm i
|
||||||
|
COPY . .
|
||||||
|
ENV PUBLIC_PB_URL=http://pb:8080
|
||||||
|
ENV PUBLIC_URL=http://localhost:4321
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
EXPOSE 4321
|
||||||
|
# CMD ["npm", "run", "dev", "--", "--host"]
|
||||||
|
CMD [ "npm", "run", "preview", "--", "--host" ]
|
||||||
39
web/src/pages/tags/[tag].astro
Normal file
39
web/src/pages/tags/[tag].astro
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
import Base from "@layout/Base";
|
||||||
|
import { Recipe } from "@tmlmt/cooklang-parser";
|
||||||
|
import { authPB } from "@data/pb";
|
||||||
|
import Card from "@component/index/card";
|
||||||
|
|
||||||
|
const { tag } = Astro.params
|
||||||
|
const pb = await authPB()
|
||||||
|
|
||||||
|
const records = await pb.collection('recipes').getFullList({
|
||||||
|
filter: `cooklang~"- ${tag}"` // what a hack lmao but it works
|
||||||
|
})
|
||||||
|
|
||||||
|
const recipes = records.map(r => new Recipe(r.cooklang))
|
||||||
|
const ids = records.map(r => r.id)
|
||||||
|
const images = await Promise.all(
|
||||||
|
records.map(r => pb.files.getURL(r, r.images[0]).substring(21)) // get first image from each recipe as a cover image
|
||||||
|
)
|
||||||
|
---
|
||||||
|
|
||||||
|
<Base>
|
||||||
|
<p class="text-xl pb-2">
|
||||||
|
{recipes.length} { recipes.length == 1 ? "Recipe" : "Recipes" } with: <code class="bg-white/10 p-1 text-sm rounded-lg" >{tag}</code>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="grid md:gap-2 gap-3 grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-8">
|
||||||
|
{
|
||||||
|
recipes.map((r, i) => (
|
||||||
|
<Card
|
||||||
|
id={ids[i]}
|
||||||
|
title={r.metadata.title ?? "Untitled Recipe"}
|
||||||
|
description={r.metadata.description ?? "No Description"}
|
||||||
|
tags={r.metadata.tags ?? []}
|
||||||
|
image={images[i]}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Base>
|
||||||
46
web/src/pages/tags/index.astro
Normal file
46
web/src/pages/tags/index.astro
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
import { authPB } from "@data/pb";
|
||||||
|
import { Recipe } from "@tmlmt/cooklang-parser";
|
||||||
|
import Base from "@layout/Base";
|
||||||
|
|
||||||
|
const pb = await authPB()
|
||||||
|
|
||||||
|
// Get all recipes
|
||||||
|
const records = await pb.collection('recipes').getFullList()
|
||||||
|
|
||||||
|
const recipes = records.map(r => new Recipe(r.cooklang))
|
||||||
|
|
||||||
|
// Extract all tags and count occurrences
|
||||||
|
const tagCounts = new Map<string, number>();
|
||||||
|
|
||||||
|
recipes.forEach(recipe => {
|
||||||
|
const recipeTags = recipe.metadata?.tags;
|
||||||
|
if (Array.isArray(recipeTags)) {
|
||||||
|
recipeTags.forEach(tag => {
|
||||||
|
if (tag && typeof tag === 'string') {
|
||||||
|
const trimmedTag = tag.trim();
|
||||||
|
tagCounts.set(trimmedTag, (tagCounts.get(trimmedTag) || 0) + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert to array and sort by name
|
||||||
|
const tags = Array.from(tagCounts.keys()).sort();
|
||||||
|
const countsPerTag = tags.map(tag => tagCounts.get(tag) || 0);
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<Base>
|
||||||
|
<p class="title pb-2">
|
||||||
|
{tags.length} Tags
|
||||||
|
</p>
|
||||||
|
{
|
||||||
|
(tags ?? []).map((t, i) => (
|
||||||
|
// <p>{t.name} -> {countsPerTag[i]} {countsPerTag[i] == 1 ? "Recipe" : "Recipes" } </p>
|
||||||
|
<a class="hover:underline" href={`/tags/${t}`}>
|
||||||
|
{t} ({countsPerTag[i]})
|
||||||
|
</a><br/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Base>
|
||||||
Reference in New Issue
Block a user