├── .nvmrc ├── src ├── content │ ├── topic │ │ ├── docs.md │ │ ├── front-end.md │ │ ├── user-experience.md │ │ └── ai.md │ ├── author │ │ ├── anonymous.md │ │ └── eddy-vinck.md │ ├── config.ts │ └── blog │ │ ├── using-mdx.mdx │ │ ├── third-post.md │ │ ├── second-post.md │ │ ├── first-post.md │ │ ├── markdown-style-guide.md │ │ ├── enabling-emoji-reactions.md │ │ └── engineering-blog.md ├── assets │ └── images │ │ ├── author │ │ ├── anonymous.jpg │ │ └── eddy-vinck.jpg │ │ └── blog │ │ ├── readme │ │ └── readme.jpg │ │ └── enabling-emoji-reactions │ │ ├── post-reactions-collection.png │ │ ├── post-reactions-for-article.png │ │ └── markdown-and-appwrite-matching-id.png ├── components │ ├── Heading.astro │ ├── Footer.astro │ ├── FormattedDate.astro │ ├── Searchbar.astro │ ├── HeaderDropdownLink.astro │ ├── PostList.astro │ ├── LatestPosts.astro │ ├── HeaderLink.astro │ ├── Post.astro │ ├── HeaderDropdown.astro │ ├── backend-services │ │ ├── EmojiReactions.astro │ │ ├── RankedPost.astro │ │ └── EmojiReactionsButtons.tsx │ ├── PostListItem.tsx │ ├── BaseHead.astro │ ├── TopicList.astro │ ├── AuthorList.astro │ ├── DarkModeToggle.astro │ ├── AuthorInfo.astro │ ├── Search.tsx │ └── Header.astro ├── pages │ ├── search.astro │ ├── 404.astro │ ├── topics │ │ ├── index.astro │ │ ├── uncategorized.astro │ │ └── [...slug].astro │ ├── rss.xml.js │ ├── authors │ │ └── index.astro │ ├── blog │ │ ├── [post].astro │ │ └── [...page].astro │ ├── api │ │ ├── post-reactions-ranked.ts │ │ └── post-reactions │ │ │ └── [post].ts │ ├── index.astro │ ├── blog-ranking.astro │ └── about.md ├── consts.ts ├── env.d.ts ├── layouts │ ├── Page.astro │ ├── TransitionContent.astro │ └── BlogPost.astro ├── lib │ ├── ratelimit.ts │ └── appwrite │ │ └── appwrite.server.ts └── styles │ └── global.css ├── public ├── placeholder-hero.jpg ├── placeholder-about.jpg ├── placeholder-social.jpg └── favicon.svg ├── .vscode ├── extensions.json └── launch.json ├── .netlify └── v1 │ └── config.json ├── tsconfig.json ├── .env.example ├── .gitignore ├── astro.config.mjs ├── tailwind.config.cjs ├── LICENSE ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 18.18.0 -------------------------------------------------------------------------------- /src/content/topic/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Documentation 3 | --- 4 | -------------------------------------------------------------------------------- /src/content/topic/front-end.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Front-end 3 | --- 4 | 5 | Front-end web development 6 | -------------------------------------------------------------------------------- /src/content/topic/user-experience.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: User Experience 3 | --- 4 | 5 | User Experience is important 6 | -------------------------------------------------------------------------------- /public/placeholder-hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EddyVinck/astro-engineering-blog/HEAD/public/placeholder-hero.jpg -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /public/placeholder-about.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EddyVinck/astro-engineering-blog/HEAD/public/placeholder-about.jpg -------------------------------------------------------------------------------- /public/placeholder-social.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EddyVinck/astro-engineering-blog/HEAD/public/placeholder-social.jpg -------------------------------------------------------------------------------- /src/assets/images/author/anonymous.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EddyVinck/astro-engineering-blog/HEAD/src/assets/images/author/anonymous.jpg -------------------------------------------------------------------------------- /src/assets/images/author/eddy-vinck.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EddyVinck/astro-engineering-blog/HEAD/src/assets/images/author/eddy-vinck.jpg -------------------------------------------------------------------------------- /src/assets/images/blog/readme/readme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EddyVinck/astro-engineering-blog/HEAD/src/assets/images/blog/readme/readme.jpg -------------------------------------------------------------------------------- /.netlify/v1/config.json: -------------------------------------------------------------------------------- 1 | {"images":{"remote_images":[]},"headers":[{"for":"/_astro/*","values":{"Cache-Control":"public, max-age=31536000, immutable"}}]} -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strictest", 3 | "compilerOptions": { 4 | "strictNullChecks": true, 5 | "jsx": "preserve", 6 | "jsxImportSource": "solid-js" 7 | } 8 | } -------------------------------------------------------------------------------- /src/assets/images/blog/enabling-emoji-reactions/post-reactions-collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EddyVinck/astro-engineering-blog/HEAD/src/assets/images/blog/enabling-emoji-reactions/post-reactions-collection.png -------------------------------------------------------------------------------- /src/assets/images/blog/enabling-emoji-reactions/post-reactions-for-article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EddyVinck/astro-engineering-blog/HEAD/src/assets/images/blog/enabling-emoji-reactions/post-reactions-for-article.png -------------------------------------------------------------------------------- /src/assets/images/blog/enabling-emoji-reactions/markdown-and-appwrite-matching-id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EddyVinck/astro-engineering-blog/HEAD/src/assets/images/blog/enabling-emoji-reactions/markdown-and-appwrite-matching-id.png -------------------------------------------------------------------------------- /src/components/Heading.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const H: any = `h${Astro.props.headingLevel}`; 3 | 4 | type Props = { 5 | headingLevel: 1 | 2 | 3 | 4 | 5 | 6; 6 | class?: string; 7 | }; 8 | --- 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/Footer.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { BRAND } from "../consts"; 3 | 4 | const today = new Date(); 5 | --- 6 | 7 | 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SECRET_APPWRITE_API_KEY=YOUR_APPWRITE_API_KEY 2 | PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 3 | PUBLIC_APPWRITE_PROJECT_ID=PUBLIC_APPWRITE_PROJECT_ID 4 | PUBLIC_APPWRITE_DATABASE_ID=PUBLIC_APPWRITE_DATABASE_ID 5 | PUBLIC_APPWRITE_EMOJI_REACTIONS_COLLECTION_ID=post-reactions -------------------------------------------------------------------------------- /src/components/FormattedDate.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export interface Props { 3 | date: Date; 4 | } 5 | 6 | const { date } = Astro.props; 7 | --- 8 | 9 | 18 | -------------------------------------------------------------------------------- /src/pages/search.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../layouts/Page.astro"; 3 | import Searchbar from "../components/Searchbar.astro"; 4 | --- 5 | 6 | 7 |

Search

8 |

Search for any title, description or post content

9 | 10 |
11 | -------------------------------------------------------------------------------- /src/pages/404.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../layouts/Page.astro"; 3 | --- 4 | 5 | 9 |

404 - not found :(

10 |
11 | Go to the homepage 12 |
13 |
14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | .vscode/settings.json -------------------------------------------------------------------------------- /src/components/Searchbar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getCollection } from "astro:content"; 3 | import Search from "./Search"; 4 | 5 | const posts = (await getCollection("blog")) 6 | .filter((post) => post.data.draft === false) 7 | .sort((a, b) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf()); 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/pages/topics/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../../layouts/Page.astro"; 3 | import TopicList from "../../components/TopicList.astro"; 4 | --- 5 | 6 | 10 |

All the topics from our blog

11 | 12 |
13 | -------------------------------------------------------------------------------- /src/consts.ts: -------------------------------------------------------------------------------- 1 | // Place any global data in this file. 2 | // You can import this data from anywhere in your site by using the `import` keyword. 3 | 4 | export const BRAND = "Acme"; 5 | export const SITE_TITLE = "Astro Engineering Blog"; 6 | export const SITE_URL = "https://astro-engineering-blog.netlify.app"; 7 | export const SITE_DESCRIPTION = "Welcome to our Engineering blog!"; 8 | export const PAGINATION_POSTS_PER_PAGE = 5; 9 | -------------------------------------------------------------------------------- /src/content/author/anonymous.md: -------------------------------------------------------------------------------- 1 | --- 2 | firstname: Anonymous 3 | lastname: "" 4 | avatar: "../../assets/images/author/anonymous.jpg" # will resolve to "src/content/assets/authors/{author}.jpg" 5 | --- 6 | 7 | Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. 8 | -------------------------------------------------------------------------------- /src/components/HeaderDropdownLink.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types"; 3 | import HeaderLink from "./HeaderLink.astro"; 4 | 5 | type Props = HTMLAttributes<"a">; 6 | 7 | const { href } = Astro.props; 8 | const className = Astro.props.class ? ` ${Astro.props.class}` : ""; 9 | --- 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | interface ImportMetaEnv { 5 | readonly SECRET_APPWRITE_API_KEY: string; 6 | readonly PUBLIC_APPWRITE_ENDPOINT: string; 7 | readonly PUBLIC_APPWRITE_PROJECT_ID: string; 8 | readonly PUBLIC_APPWRITE_DATABASE_ID: string; 9 | readonly PUBLIC_APPWRITE_EMOJI_REACTIONS_COLLECTION_ID: string; 10 | } 11 | 12 | interface ImportMeta { 13 | readonly env: ImportMetaEnv; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/PostList.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { CollectionEntry } from "astro:content"; 3 | import Post from "./Post.astro"; 4 | 5 | type Props = { 6 | posts: CollectionEntry<"blog">[]; 7 | }; 8 | 9 | const { posts } = Astro.props; 10 | --- 11 | 12 |
    13 | { 14 | posts.map((post) => { 15 | return ( 16 |
  • 17 | 18 |
  • 19 | ); 20 | }) 21 | } 22 |
23 | -------------------------------------------------------------------------------- /src/components/LatestPosts.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getCollection } from "astro:content"; 3 | import PostList from "./PostList.astro"; 4 | 5 | const posts = (await getCollection("blog")) 6 | .filter((post) => post.data.draft === false) 7 | .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()) 8 | .slice(0, 3); 9 | --- 10 | 11 |
12 |

Latest

13 |
14 | {posts.length > 0 && } 15 | See all posts 16 |
17 |
18 | -------------------------------------------------------------------------------- /src/content/author/eddy-vinck.md: -------------------------------------------------------------------------------- 1 | --- 2 | firstname: Eddy 3 | lastname: Vinck 4 | avatar: "../../assets/images/author/eddy-vinck.jpg" # will resolve to "src/content/assets/authors/{author}.jpg" 5 | twitter: https://twitter.com/eddyvinckk 6 | linkedin: https://linkedin.com/in/eddyvinck 7 | github: https://github.com/EddyVinck 8 | --- 9 | 10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. 11 | -------------------------------------------------------------------------------- /src/pages/rss.xml.js: -------------------------------------------------------------------------------- 1 | import rss from "@astrojs/rss"; 2 | import { getCollection } from "astro:content"; 3 | import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; 4 | 5 | export async function GET(context) { 6 | const posts = await getCollection("blog"); 7 | return rss({ 8 | title: SITE_TITLE, 9 | description: SITE_DESCRIPTION, 10 | site: context.site, 11 | items: posts 12 | .filter((post) => post.data.draft === false) 13 | .map((post) => ({ 14 | ...post.data, 15 | link: `/blog/${post.slug}/`, 16 | })), 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /src/pages/authors/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../../layouts/Page.astro"; 3 | import AuthorList from "../../components/AuthorList.astro"; 4 | --- 5 | 6 | 10 |

Authors

11 | 17 |
18 | 19 | 24 | -------------------------------------------------------------------------------- /src/content/topic/ai.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Artificial Intelligence 3 | --- 4 | 5 | Artificial Intelligence (AI) is rapidly changing the way we live and work, with new advancements and innovations emerging every day. This blog will delve into the fascinating world of AI, discussing its current capabilities and potential future applications across industries such as healthcare, finance, and transportation. We will explore the benefits and challenges of this technology, as well as its ethical implications and impact on society. Join us on this journey to discover how AI is shaping the world around us, and what the future may hold for this exciting field. 6 | -------------------------------------------------------------------------------- /src/components/HeaderLink.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types"; 3 | 4 | type Props = HTMLAttributes<"a">; 5 | 6 | const { href, class: className, ...props } = Astro.props; 7 | 8 | const { pathname } = Astro.url; 9 | const isActive = href === pathname || href === pathname.replace(/\/$/, ""); 10 | --- 11 | 12 |
  • 13 | 22 | 23 | 24 | 25 |
  • 26 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | import mdx from "@astrojs/mdx"; 3 | import sitemap from "@astrojs/sitemap"; 4 | import solidJs from "@astrojs/solid-js"; 5 | import tailwind from "@astrojs/tailwind"; 6 | import netlify from "@astrojs/netlify"; 7 | import { SITE_URL } from "./src/consts"; 8 | 9 | // https://astro.build/config 10 | export default defineConfig({ 11 | output: "hybrid", 12 | adapter: netlify({ 13 | edgeMiddleware: false, 14 | imageCDN: false, // Blog hero image doesn't seem to work with this enabled 15 | }), 16 | site: SITE_URL, 17 | integrations: [ 18 | mdx(), 19 | sitemap(), 20 | solidJs({ 21 | include: "**.tsx", 22 | }), 23 | tailwind(), 24 | ], 25 | }); 26 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/Post.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { type CollectionEntry, getCollection } from "astro:content"; 3 | import { PostListItem } from "./PostListItem"; 4 | 5 | type Props = { 6 | post: CollectionEntry<"blog">; 7 | }; 8 | 9 | const { post } = Astro.props; 10 | const { data } = post; 11 | const { authors: authorIds } = data; 12 | 13 | const authorData = await getCollection("author"); 14 | const authors = authorData 15 | .filter((author) => authorIds.includes(author.slug)) 16 | .map( 17 | (author) => 18 | `${author.data.firstname}${ 19 | author.data.lastname ? ` ${author.data.lastname}` : "" 20 | }` 21 | ); 22 | 23 | const finalPost: CollectionEntry<"blog"> = { 24 | ...post, 25 | data: { 26 | ...data, 27 | authors, 28 | }, 29 | }; 30 | --- 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/pages/blog/[post].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { GetStaticPaths } from "astro"; 3 | import { getCollection, type CollectionEntry, getEntry } from "astro:content"; 4 | import BlogPost from "../../layouts/BlogPost.astro"; 5 | 6 | export const prerender = true; 7 | 8 | export const getStaticPaths: GetStaticPaths = async () => { 9 | const posts = await getCollection("blog"); 10 | return posts 11 | .filter((post) => post.data.draft === false) 12 | .map((entry) => ({ 13 | params: { post: entry.slug }, 14 | props: { entry }, 15 | })); 16 | }; 17 | 18 | export type Props = { 19 | entry: CollectionEntry<"blog">; 20 | }; 21 | 22 | const { entry } = Astro.props; 23 | 24 | const blogpost = await getEntry("blog", entry.slug); 25 | const { Content } = await blogpost.render(); 26 | --- 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/pages/api/post-reactions-ranked.ts: -------------------------------------------------------------------------------- 1 | import { type APIRoute } from "astro"; 2 | import { getPostReactionsRanked } from "../../lib/appwrite/appwrite.server"; 3 | 4 | export const prerender = false; 5 | 6 | export const GET: APIRoute = async (): Promise => { 7 | if (!import.meta.env.SECRET_APPWRITE_API_KEY) { 8 | return new Response(JSON.stringify({ error: "internal server error" }), { 9 | status: 500, 10 | }); 11 | } 12 | 13 | let postReactionsRanked = null; 14 | postReactionsRanked = await getPostReactionsRanked(); 15 | if (!postReactionsRanked) { 16 | return new Response(null, { 17 | status: 404, 18 | statusText: "Not found", 19 | }); 20 | } 21 | 22 | return new Response(JSON.stringify(postReactionsRanked), { 23 | status: 200, 24 | headers: { 25 | "Content-Type": "application/json", 26 | }, 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /src/layouts/Page.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseHead from "../components/BaseHead.astro"; 3 | import Header from "../components/Header.astro"; 4 | import Footer from "../components/Footer.astro"; 5 | import { ViewTransitions } from "astro:transitions"; 6 | 7 | type Props = { 8 | title: string; 9 | description: string; 10 | frontmatter?: { 11 | title: string; 12 | description: string; 13 | }; 14 | }; 15 | 16 | const { frontmatter } = Astro.props; 17 | const title = frontmatter?.title || Astro.props.title; 18 | const description = frontmatter?.description || Astro.props.description; 19 | --- 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
    29 |
    30 | 31 |
    32 |