diff --git a/.gitignore b/.gitignore index 016b59e..f563b5e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ pnpm-debug.log* # jetbrains setting folder .idea/ + +#local +local diff --git a/package-lock.json b/package-lock.json index 1fafb81..725c8eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,16 @@ "name": "menix-website", "version": "0.0.1", "dependencies": { + "@astrojs/rss": "^4.0.14", "@astrojs/starlight": "^0.36.2", "@astrojs/starlight-tailwind": "^4.0.2", "@tailwindcss/vite": "^4.1.17", "astro": "^5.15.4", + "remove-markdown": "^0.6.2", "tailwindcss": "^4.1.17" }, "devDependencies": { + "@tailwindcss/typography": "^0.5.19", "prettier": "3.6.2", "prettier-plugin-astro": "0.14.1" } @@ -99,6 +102,16 @@ "node": "18.20.8 || ^20.3.0 || >=22.0.0" } }, + "node_modules/@astrojs/rss": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.14.tgz", + "integrity": "sha512-KCe1imDcADKOOuO/wtKOMDO/umsBD6DWF+94r5auna1jKl5fmlK9vzf+sjA3EyveXA/FoB3khtQ/u/tQgETmTw==", + "license": "MIT", + "dependencies": { + "fast-xml-parser": "^5.3.0", + "piccolore": "^0.1.3" + } + }, "node_modules/@astrojs/sitemap": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.6.0.tgz", @@ -115,7 +128,6 @@ "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.36.2.tgz", "integrity": "sha512-QR8NfO7+7DR13kBikhQwAj3IAoptLLNs9DkyKko2M2l3PrqpcpVUnw1JBJ0msGDIwE6tBbua2UeBND48mkh03w==", "license": "MIT", - "peer": true, "dependencies": { "@astrojs/markdown-remark": "^6.3.1", "@astrojs/mdx": "^4.2.3", @@ -1995,6 +2007,33 @@ "node": ">= 10" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@tailwindcss/vite": { "version": "4.1.17", "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.17.tgz", @@ -2122,7 +2161,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2283,7 +2321,6 @@ "resolved": "https://registry.npmjs.org/astro/-/astro-5.15.4.tgz", "integrity": "sha512-0g/68hLHEJZF2nYUcZM5O0kOnzCsCIf8eA9+0jfBAxp4ycujrIHRgIOdZCFKL9GoTsn8AypWbziypH5aEIF+aA==", "license": "MIT", - "peer": true, "dependencies": { "@astrojs/compiler": "^2.12.2", "@astrojs/internal-helpers": "0.7.4", @@ -3085,6 +3122,24 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.2.tgz", + "integrity": "sha512-n8v8b6p4Z1sMgqRmqLJm3awW4NX7NkaKPfb3uJIBTSH7Pdvufi3PQ3/lJLQrvxcMYl7JI2jnDO90siPEpD8JBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -5421,6 +5476,12 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5458,7 +5519,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5889,6 +5949,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remove-markdown": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.6.2.tgz", + "integrity": "sha512-EijDXJZbzpGbQBd852ViUzcqgpMujthM+SAEHiWCMcZonRbZ+xViWKLJA/vrwbDwYdxrs1aFDjpBhcGrZoJRGA==", + "license": "MIT" + }, "node_modules/restructure": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", @@ -5961,7 +6027,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -6217,6 +6282,18 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/style-to-js": { "version": "1.1.19", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.19.tgz", @@ -6249,8 +6326,7 @@ "version": "4.1.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", @@ -6727,7 +6803,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -6926,7 +7001,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 423a1c6..47d243c 100644 --- a/package.json +++ b/package.json @@ -8,16 +8,19 @@ "preview": "astro preview", "astro": "astro", "format": "prettier --write .", - "format:check": "prettier ." + "format:check": "prettier --check ." }, "dependencies": { + "@astrojs/rss": "^4.0.14", "@astrojs/starlight": "^0.36.2", "@astrojs/starlight-tailwind": "^4.0.2", "@tailwindcss/vite": "^4.1.17", "astro": "^5.15.4", + "remove-markdown": "^0.6.2", "tailwindcss": "^4.1.17" }, "devDependencies": { + "@tailwindcss/typography": "^0.5.19", "prettier": "3.6.2", "prettier-plugin-astro": "0.14.1" } diff --git a/src/assets/icons/rss.svg b/src/assets/icons/rss.svg new file mode 100644 index 0000000..54d5edb --- /dev/null +++ b/src/assets/icons/rss.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/assets/images/lighthouse-scores.png b/src/assets/images/lighthouse-scores.png new file mode 100644 index 0000000..1d9c01f Binary files /dev/null and b/src/assets/images/lighthouse-scores.png differ diff --git a/src/components/BlogContentFormatting.astro b/src/components/BlogContentFormatting.astro new file mode 100644 index 0000000..a476a9d --- /dev/null +++ b/src/components/BlogContentFormatting.astro @@ -0,0 +1,5 @@ +
+ +
diff --git a/src/components/BlogPostCard.astro b/src/components/BlogPostCard.astro new file mode 100644 index 0000000..f0f576b --- /dev/null +++ b/src/components/BlogPostCard.astro @@ -0,0 +1,32 @@ +--- +import type { BlogPostInfo } from "../scripts/blogpost"; + +interface Props { + post: BlogPostInfo; +} + +const { post } = Astro.props; + +const PREVIEW_LEN = 150; + +let contentPreview = post.markdownRemovedContent; + +if (post.rawContent.length > PREVIEW_LEN) { + contentPreview = contentPreview.substring(0, 150) + "..."; +} +--- + + +
+
+ {post.title} +
+ + + {post.author}, {post.date.toLocaleDateString("de-EN")} + +
+ {contentPreview} +
+
+
diff --git a/src/components/BlogPostList.astro b/src/components/BlogPostList.astro new file mode 100644 index 0000000..36c45b9 --- /dev/null +++ b/src/components/BlogPostList.astro @@ -0,0 +1,39 @@ +--- +import { type BlogPostInfo } from "../scripts/blogpost"; +import BlogPostCard from "./BlogPostCard.astro"; + +interface Props { + posts: BlogPostInfo[]; + maxItems?: number; + sort?: "date" | "dateDesc" | "default"; +} + +let { maxItems, sort = "date", posts } = Astro.props; + +function sortByDate() { + posts.sort(function (a, b) { + return new Date(b.date).getTime() - new Date(a.date).getTime(); + }); +} + +if (sort === "date") { + sortByDate(); +} else if (sort === "dateDesc") { + sortByDate(); + posts.reverse(); +} + +if (maxItems) { + posts = posts.slice(0, maxItems); +} +--- + + diff --git a/src/components/Footer.astro b/src/components/Footer.astro index a797186..d46c2bd 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -4,6 +4,7 @@ import MenixAnimatedLogo from "./reusable/MenixAnimatedLogo.astro"; import Factory from "../assets/icons/factory.svg"; import GhLogo from "../assets/icons/github-logo.svg"; import Download from "../assets/icons/download.svg"; +import RssIcon from "../assets/icons/rss.svg"; const links = [ { @@ -18,9 +19,14 @@ const links = [ }, { icon: GhLogo, - text: "Github org", + text: "Github", href: "https://github.com/menix-os", }, + { + icon: RssIcon, + text: "RSS", + href: "https://menix-os.org/rss.xml", + }, ]; --- diff --git a/src/components/Frontpage.astro b/src/components/Frontpage.astro index 6c29fa9..4e743c3 100644 --- a/src/components/Frontpage.astro +++ b/src/components/Frontpage.astro @@ -3,8 +3,12 @@ import { Image } from "astro:assets"; import HeroImage from "../assets/images/menix-in-qemu-cropped.png"; import Button from "./reusable/Button.astro"; import MenixGear from "../assets/logos/menix-gear.svg"; +import BlogPostList from "./BlogPostList.astro"; +import { loadPostInfos } from "../scripts/blogpost"; const gearSize = 350; + +const posts = loadPostInfos(); ---
@@ -44,6 +48,17 @@ const gearSize = 350; /> + +
+
+

Recent blog posts

+
+
+ +
+ +
+
diff --git a/src/pages/blog.astro b/src/pages/blog.astro new file mode 100644 index 0000000..452295b --- /dev/null +++ b/src/pages/blog.astro @@ -0,0 +1,35 @@ +--- +import Layout from "../layouts/Layout.astro"; +import BlogPostList from "../components/BlogPostList.astro"; +import { loadPostInfos } from "../scripts/blogpost"; + +const posts = loadPostInfos(); +--- + + +
+
+ { + posts.length > 0 && ( + <> +

+ Menix blog posts ({posts.length}) +

+ +
+ + + + ) + } + + { + posts.length < 1 && ( +

There are no posts yet :(

+ ) + } +
+
+
diff --git a/src/pages/blog/a-new-website.md b/src/pages/blog/a-new-website.md new file mode 100644 index 0000000..adaf8c3 --- /dev/null +++ b/src/pages/blog/a-new-website.md @@ -0,0 +1,22 @@ +--- +layout: ../../layouts/BlogPostLayout.astro +title: "A new Website" +date: 2025-11-23 +lastEdited: null +author: "PotatoMaaan" +--- + +Menix now has a new website! It's made up entirely of static assets and doesn't ship any javascript to the client. It's currently hosted by Github Pages. + +## Tech Stack + +- The main framework is [Astro](https://astro.build/) +- [Starlight](https://starlight.astro.build/) for the handbook pages +- [Tailwind](https://tailwindcss.com/) for styling +- [Astro's RSS plugin](https://www.npmjs.com/package/@astrojs/rss) to easily set up our [RSS feed](https://menix-os.org/rss.xml). + +As one would expect, this allows us to hit great lighthouse scores :D + +![screenshot of good lighthouse scores](../../assets/images/lighthouse-scores.png) + +You can find the code [on our Github](https://github.com/menix-os/website). diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts new file mode 100644 index 0000000..55d7b92 --- /dev/null +++ b/src/pages/rss.xml.ts @@ -0,0 +1,11 @@ +import rss, { pagesGlobToRssItems } from "@astrojs/rss"; + +export async function GET(context: any) { + return rss({ + title: "Menix Blog", + description: "Blog posts from the Menix project", + site: context.site, + items: await pagesGlobToRssItems(import.meta.glob("./blog/*.md")), + customData: `en-us`, + }); +} diff --git a/src/scripts/blogpost.ts b/src/scripts/blogpost.ts new file mode 100644 index 0000000..a6e8c3d --- /dev/null +++ b/src/scripts/blogpost.ts @@ -0,0 +1,43 @@ +import { removeMarkdown } from "./markdown-remoal"; + +export interface BlogPostInfo { + title: string; + date: Date; + author: string; + url: string; + img?: string; + rawContent: string; + markdownRemovedContent: string; + lastEdited: Date | null; +} + +export function postInfoFromRawPosts(raw: any[]): BlogPostInfo[] { + return raw.map((post) => { + const rawContent: string = post.rawContent().trim(); + + const markdownRemovedContent = removeMarkdown(rawContent); + + const lastEdited: string | null = post.frontmatter.lastEdited; + + return { + author: post.frontmatter.author, + date: new Date(post.frontmatter.date), + title: post.frontmatter.title, + img: post.frontmatter.img, + url: post.url, + rawContent, + markdownRemovedContent, + lastEdited: lastEdited ? new Date(lastEdited) : null, + }; + }); +} + +export function loadRawPosts(): any[] { + return Object.values( + import.meta.glob("../pages/blog/*.md", { eager: true }), + ); +} + +export function loadPostInfos(): BlogPostInfo[] { + return postInfoFromRawPosts(loadRawPosts()); +} diff --git a/src/scripts/markdown-remoal.ts b/src/scripts/markdown-remoal.ts new file mode 100644 index 0000000..eaaa8b7 --- /dev/null +++ b/src/scripts/markdown-remoal.ts @@ -0,0 +1,5 @@ +import removeMd from "remove-markdown"; + +export function removeMarkdown(markdown: string): string { + return removeMd(markdown); +} diff --git a/src/styles/custom-tailwind.css b/src/styles/custom-tailwind.css index df611ba..0822bb0 100644 --- a/src/styles/custom-tailwind.css +++ b/src/styles/custom-tailwind.css @@ -1,4 +1,24 @@ @import "tailwindcss"; +@plugin "@tailwindcss/typography"; + +/* incredibly weird workaround needed because the +large code blocks were not targetet by +prose-code, prose-pre or prose-figure. +This is gpt slop so there is proably a better way to figure this out +*/ +@layer components { + /* Light theme default styling for code blocks */ + .prose pre { + background-color: theme("colors.zinc.100") !important; + } + + /* Dark theme override */ + @media (prefers-color-scheme: dark) { + .prose pre { + background-color: theme("colors.zinc.900") !important; + } + } +} @font-face { font-family: rubik-glitch-regular; @@ -50,7 +70,7 @@ @theme { --font-glitch: rubik-glitch-regular, sans-serif; - --font-rubik: rubik, monospace; + --font-rubik: rubik, sans-serif, monospace; --font-scp: source-code-pro, sans-serif; /* base color from menix logo #FF1A00 */