Compare commits

...

5 Commits

Author SHA1 Message Date
vorpax
d9862eae65 feat: add support for multiple content types in index page and sorting functions
Some checks failed
Build and Push Docker Image - HEC IA Wiki / build-and-push (push) Has been cancelled
2026-01-29 11:51:55 +01:00
vorpax
dd4f6a7b01 feat: add event, news, technical, and workshop pages with pagination and details 2026-01-29 11:46:37 +01:00
vorpax
d953687389 feat: expand content types in Card, EditPost, Pagination, and PostDetails components 2026-01-29 11:46:07 +01:00
vorpax
96d5300d95 fix: fix broken README.md 2026-01-29 11:45:03 +01:00
vorpax
6971fc231d feat: initialize HEC IA Wiki with Docker setup, build process, and content structure
- Add .dockerignore t
- Create Gitea Action to build and publish image
- Update Dockerfile to include build steps and health check
- Revise README.md
- Modify config.ts for site-specific settings
- Enhance content.config.ts to define collections for events, workshops, news, and technical articles
2026-01-29 11:44:34 +01:00
24 changed files with 761 additions and 215 deletions

View File

@@ -1,8 +1,27 @@
# Ignore Astro files
*.astro
# Development files
.git/
.gitignore
.vscode/
.DS_Store
# Ignore node_modules directory
# Cache and temp files
.astro/
node_modules/
# Ignore build output
dist/
# Build output (will be rebuilt in Docker)
dist/
# Environment files
.env
.env.*
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Documentation (optional - comment if needed in image)
# *.md
# !README.md

View File

@@ -0,0 +1,46 @@
name: Build and Push Docker Image - HEC IA Wiki
on:
push:
branches:
- main
- master
env:
REGISTRY: "gitea.vorpax.dev"
IMAGE_NAME: "vorpax_admin/hecia-wiki"
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=sha,prefix=
type=raw,value=latest,enable=${{ gitea.ref == format('refs/heads/{0}', 'main') || gitea.ref == format('refs/heads/{0}', 'master') }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -0,0 +1,11 @@
name: Debug Context
on: workflow_dispatch
jobs:
debug:
runs-on: ubuntu-latest
steps:
- name: Print Gitea context
run: |
echo "server_url: ${{ gitea.server_url }}"
echo "api_url: ${{ gitea.api_url }}"
echo "repository: ${{ gitea.repository }}"

3
.gitignore vendored
View File

@@ -25,4 +25,5 @@ pnpm-debug.log*
# pagefind
public/pagefind
public/pagefind
SETUP.md

View File

@@ -5,13 +5,32 @@ WORKDIR /app
# Install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# Copy package files
COPY package.json pnpm-lock.yaml ./
# Install dependencies
RUN pnpm install --frozen-lockfile
# Copy all source files (respecting .dockerignore)
COPY . .
# Build the application
# Note: The build script includes: astro check && astro build && pagefind --site dist && cp -r dist/pagefind public/
RUN pnpm run build
# Runtime stage for serving the application
FROM nginx:mainline-alpine-slim AS runtime
# Copy the built site from dist directory
COPY --from=base /app/dist /usr/share/nginx/html
# The build script copies pagefind to public/, so we need to copy it back to dist for the runtime
# Copy pagefind if it exists in public/
COPY --from=base /app/public/pagefind /usr/share/nginx/html/pagefind
# Expose port 80
EXPOSE 80
# Add healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1

333
README.md
View File

@@ -1,180 +1,211 @@
# AstroPaper 📄
# HEC IA Wiki 🤖
![AstroPaper](public/astropaper-og.jpg)
[![Figma](https://img.shields.io/badge/Figma-F24E1E?style=for-the-badge&logo=figma&logoColor=white)](https://www.figma.com/community/file/1356898632249991861)
![Typescript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)
![GitHub](https://img.shields.io/github/license/satnaing/astro-paper?color=%232F3741&style=for-the-badge)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white&style=for-the-badge)](https://conventionalcommits.org)
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=for-the-badge)](http://commitizen.github.io/cz-cli/)
![HEC IA Wiki](public/hec-ia-og.jpg)
AstroPaper is a minimal, responsive, accessible and SEO-friendly Astro blog theme. This theme is designed and crafted based on [my personal blog](https://satnaing.dev/blog).
The official wiki and knowledge base for HEC IA student association. Built with [Astro](https://astro.build/) and the [AstroPaper](https://github.com/satnaing/astro-paper) theme.
Read [the blog posts](https://astro-paper.pages.dev/posts/) or check [the README Documentation Section](#-documentation) for more info.
## 📚 About
## 🔥 Features
This wiki serves as a central hub for HEC IA's content, including:
- [x] type-safe markdown
- [x] super fast performance
- [x] accessible (Keyboard/VoiceOver)
- [x] responsive (mobile ~ desktops)
- [x] SEO-friendly
- [x] light & dark mode
- [x] fuzzy search
- [x] draft posts & pagination
- [x] sitemap & rss feed
- [x] followed best practices
- [x] highly customizable
- [x] dynamic OG image generation for blog posts [#15](https://github.com/satnaing/astro-paper/pull/15) ([Blog Post](https://astro-paper.pages.dev/posts/dynamic-og-image-generation-in-astropaper-blog-posts/))
- **Events**: Information about upcoming and past events
- **Workshops**: Hands-on technical workshops and tutorials
- **News**: Latest updates and announcements from HEC IA
- **Technical Deep Dives**: In-depth technical articles on AI topics
_Note: I've tested screen-reader accessibility of AstroPaper using **VoiceOver** on Mac and **TalkBack** on Android. I couldn't test all other screen-readers out there. However, accessibility enhancements in AstroPaper should be working fine on others as well._
## 🚀 Quick Start
## ✅ Lighthouse Score
### Prerequisites
<p align="center">
<a href="https://pagespeed.web.dev/report?url=https%3A%2F%2Fastro-paper.pages.dev%2F&form_factor=desktop">
<img width="710" alt="AstroPaper Lighthouse Score" src="AstroPaper-lighthouse-score.svg">
</a>
</p>
- Node.js 18+
- npm or pnpm
## 🚀 Project Structure
### Installation
Inside of AstroPaper, you'll see the following folders and files:
```bash
# Clone the repository
git clone https://github.com/hec-ia/wiki.git
cd wiki
# Install dependencies
npm install
# Start development server
npm run dev
```
The site will be available at `http://localhost:4321`
### Building for Production
```bash
# Build the site
npm run build
# Preview the build
npm run preview
```
## 📝 Adding Content
### Events
Create a new markdown file in `src/data/events/`:
```markdown
---
title: "Your Event Title"
description: "Event description"
author: "HEC IA"
pubDatetime: 2026-01-15T10:00:00Z
eventDate: 2026-02-05T14:00:00Z
eventEndDate: 2026-02-05T18:00:00Z
location: "Event Location"
tags: ["tag1", "tag2"]
featured: true
registrationLink: "https://registration-link.com"
---
Your event content here...
```
### Workshops
Create a new markdown file in `src/data/workshops/`:
```markdown
---
title: "Workshop Title"
description: "Workshop description"
author: "HEC IA"
pubDatetime: 2026-01-15T10:00:00Z
workshopDate: 2026-02-05T14:00:00Z
duration: "3 hours"
level: "beginner" # beginner, intermediate, or advanced
tags: ["machine-learning", "python"]
featured: true
materials: "https://github.com/hec-ia/workshop-materials"
---
Your workshop content here...
```
### News
Create a new markdown file in `src/data/news/`:
```markdown
---
title: "News Title"
description: "News description"
author: "HEC IA"
pubDatetime: 2026-01-25T10:00:00Z
tags: ["news", "announcement"]
featured: true
---
Your news content here...
```
### Technical Deep Dives
Create a new markdown file in `src/data/technical/`:
```markdown
---
title: "Technical Article Title"
description: "Article description"
author: "HEC IA Technical Team"
pubDatetime: 2026-01-28T10:00:00Z
tags: ["deep-learning", "nlp"]
difficulty: "advanced" # beginner, intermediate, or advanced
readingTime: "25 min"
featured: true
---
Your technical content here...
```
## 🎨 Customization
### Site Configuration
Edit `src/config.ts` to customize:
- Site title and description
- Author information
- Social links
- Posts per page
- And more...
### Theme Colors
The site uses Tailwind CSS. Customize colors in:
- `src/styles/base.css` for global styles
- `tailwind.config.js` for theme configuration
## 📂 Project Structure
```bash
/
├── public/
── pagefind/ # auto-generated when build
│ ├── favicon.svg
│ └── astropaper-og.jpg
── assets/ # Static assets
├── src/
│ ├── assets/
│ ├── icons/
│ └── images/
│ ├── components/
│ ├── data/
│ │ ── blog/
│ │ └── some-blog-posts.md
│ ├── layouts/
│ ├── pages/
│ ├── scripts/
│ ├── styles/
│ ├── utils/
── config.ts
│ ├── constants.ts
│ ├── content.config.ts
│ ├── env.d.ts
│ └── remark-collapse.d.ts
└── astro.config.ts
│ ├── assets/ # SVG icons and images
│ ├── components/ # Astro components
├── data/ # Content collections
│ ├── events/
│ ├── workshops/
│ │ ── news/
│ │ └── technical/
│ ├── layouts/ # Page layouts
│ ├── pages/ # Route pages
│ ├── styles/ # Global styles
│ ├── utils/ # Utility functions
│ ├── config.ts # Site configuration
── content.config.ts # Content collections config
├── astro.config.ts
├── package.json
└── README.md
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
## 🧞 Available Commands
Any static assets, like images, can be placed in the `public/` directory.
| Command | Action |
| :--------------------- | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run format:check` | Check code format with Prettier |
| `npm run format` | Format codes with Prettier |
| `npm run sync` | Generates TypeScript types for all Astro modules |
| `npm run lint` | Lint with ESLint |
All blog posts are stored in `src/data/blog` directory.
## 🤝 Contributing
## 📖 Documentation
Contributions are welcome! Please feel free to submit a Pull Request.
Documentation can be read in two formats\_ _markdown_ & _blog post_.
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
- Configuration - [markdown](src/data/blog/how-to-configure-astropaper-theme.md) | [blog post](https://astro-paper.pages.dev/posts/how-to-configure-astropaper-theme/)
- Add Posts - [markdown](src/data/blog/adding-new-post.md) | [blog post](https://astro-paper.pages.dev/posts/adding-new-posts-in-astropaper-theme/)
- Customize Color Schemes - [markdown](src/data/blog/customizing-astropaper-theme-color-schemes.md) | [blog post](https://astro-paper.pages.dev/posts/customizing-astropaper-theme-color-schemes/)
- Predefined Color Schemes - [markdown](src/data/blog/predefined-color-schemes.md) | [blog post](https://astro-paper.pages.dev/posts/predefined-color-schemes/)
## 📄 License
## 💻 Tech Stack
This project is licensed under the MIT License - see the LICENSE file for details.
**Main Framework** - [Astro](https://astro.build/)
**Type Checking** - [TypeScript](https://www.typescriptlang.org/)
**Styling** - [TailwindCSS](https://tailwindcss.com/)
**UI/UX** - [Figma Design File](https://www.figma.com/community/file/1356898632249991861)
**Static Search** - [FuseJS](https://pagefind.app/)
**Icons** - [Tablers](https://tabler-icons.io/)
**Code Formatting** - [Prettier](https://prettier.io/)
**Deployment** - [Cloudflare Pages](https://pages.cloudflare.com/)
**Illustration in About Page** - [https://freesvgillustration.com](https://freesvgillustration.com/)
**Linting** - [ESLint](https://eslint.org)
## 🙏 Acknowledgments
## 👨🏻‍💻 Running Locally
Built with:
You can start using this project locally by running the following command in your desired directory:
```bash
# pnpm
pnpm create astro@latest --template satnaing/astro-paper
# pnpm
pnpm create astro@latest -- --template satnaing/astro-paper
# yarn
yarn create astro --template satnaing/astro-paper
# bun
bun create astro@latest -- --template satnaing/astro-paper
```
Then start the project by running the following commands:
```bash
# install dependencies if you haven't done so in the previous step.
pnpm install
# start running the project
pnpm run dev
```
As an alternative approach, if you have Docker installed, you can use Docker to run this project locally. Here's how:
```bash
# Build the Docker image
docker build -t astropaper .
# Run the Docker container
docker run -p 4321:80 astropaper
```
## Google Site Verification (optional)
You can easily add your [Google Site Verification HTML tag](https://support.google.com/webmasters/answer/9008080#meta_tag_verification&zippy=%2Chtml-tag) in AstroPaper using an environment variable. This step is optional. If you don't add the following environment variable, the google-site-verification tag won't appear in the HTML `<head>` section.
```bash
# in your environment variable file (.env)
PUBLIC_GOOGLE_SITE_VERIFICATION=your-google-site-verification-value
```
> See [this discussion](https://github.com/satnaing/astro-paper/discussions/334#discussioncomment-10139247) for adding AstroPaper to the Google Search Console.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
> **_Note!_** For `Docker` commands we must have it [installed](https://docs.docker.com/engine/install/) in your machine.
| Command | Action |
| :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------- |
| `pnpm install` | Installs dependencies |
| `pnpm run dev` | Starts local dev server at `localhost:4321` |
| `pnpm run build` | Build your production site to `./dist/` |
| `pnpm run preview` | Preview your build locally, before deploying |
| `pnpm run format:check` | Check code format with Prettier |
| `pnpm run format` | Format codes with Prettier |
| `pnpm run sync` | Generates TypeScript types for all Astro modules. [Learn more](https://docs.astro.build/en/reference/cli-reference/#astro-sync). |
| `pnpm run lint` | Lint with ESLint |
| `docker compose up -d` | Run AstroPaper on docker, You can access with the same hostname and port informed on `dev` command. |
| `docker compose run app pnpm install` | You can run any command above into the docker container. |
| `docker build -t astropaper .` | Build Docker image for AstroPaper. |
| `docker run -p 4321:80 astropaper` | Run AstroPaper on Docker. The website will be accessible at `http://localhost:4321`. |
> **_Warning!_** Windows PowerShell users may need to install the [concurrently package](https://www.npmjs.com/package/concurrently) if they want to [run diagnostics](https://docs.astro.build/en/reference/cli-reference/#astro-check) during development (`astro check --watch & astro dev`). For more info, see [this issue](https://github.com/satnaing/astro-paper/issues/113).
## ✨ Feedback & Suggestions
If you have any suggestions/feedback, you can contact me via [my email](mailto:contact@satnaing.dev). Alternatively, feel free to open an issue if you find bugs or want to request new features.
## 📜 License
Licensed under the MIT License, Copyright © 2025
- [Astro](https://astro.build/) - The web framework for content-driven websites
- [AstroPaper](https://github.com/satnaing/astro-paper) - The base theme
- [Tailwind CSS](https://tailwindcss.com/) - For styling
- [TypeScript](https://www.typescriptlang.org/) - For type safety
---
Made with 🤍 by [Sat Naing](https://satnaing.dev) 👨🏻‍💻 and [contributors](https://github.com/satnaing/astro-paper/graphs/contributors).
Made with ❤️ by HEC IA

View File

@@ -6,7 +6,7 @@ import Datetime from "./Datetime.astro";
type Props = {
variant?: "h2" | "h3";
} & CollectionEntry<"blog">;
} & CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">;
const { variant: Heading = "h2", id, data, filePath } = Astro.props;

View File

@@ -4,9 +4,9 @@ import IconEdit from "@/assets/icons/IconEdit.svg";
import { SITE } from "@/config";
type Props = {
hideEditPost?: CollectionEntry<"blog">["data"]["hideEditPost"];
hideEditPost?: CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">["data"]["hideEditPost"];
class?: string;
post: CollectionEntry<"blog">;
post: CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">;
};
const { hideEditPost, post, class: className = "" } = Astro.props;

View File

@@ -72,13 +72,23 @@ const isActive = (path: string) => {
]}
>
<li class="col-span-2">
<a href="/posts" class:list={{ "active-nav": isActive("/posts") }}>
Posts
<a href="/events" class:list={{ "active-nav": isActive("/events") }}>
Events
</a>
</li>
<li class="col-span-2">
<a href="/tags" class:list={{ "active-nav": isActive("/tags") }}>
Tags
<a href="/workshops" class:list={{ "active-nav": isActive("/workshops") }}>
Workshops
</a>
</li>
<li class="col-span-2">
<a href="/news" class:list={{ "active-nav": isActive("/news") }}>
News
</a>
</li>
<li class="col-span-2">
<a href="/technical" class:list={{ "active-nav": isActive("/technical") }}>
Technical
</a>
</li>
<li class="col-span-2">

View File

@@ -6,7 +6,7 @@ import IconArrowRight from "@/assets/icons/IconArrowRight.svg";
import LinkButton from "./LinkButton.astro";
type Props = {
page: Page<CollectionEntry<"blog">>;
page: Page<CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">>;
};
const { page } = Astro.props;

View File

@@ -1,10 +1,10 @@
export const SITE = {
website: "https://astro-paper.pages.dev/", // replace this with your deployed domain
author: "Sat Naing",
profile: "https://satnaing.dev/",
desc: "A minimal, responsive and SEO-friendly Astro blog theme.",
title: "AstroPaper",
ogImage: "astropaper-og.jpg",
website: "https://wiki.hec-ia.com/", // replace this with your deployed domain
author: "HEC IA",
profile: "https://wiki.hec-ia.com/about",
desc: "HEC IA student association wiki - Events, workshops, news, and technical deep dives on artificial intelligence.",
title: "HEC IA Wiki",
ogImage: "hec-ia-og.jpg",
lightAndDarkMode: true,
postPerIndex: 4,
postPerPage: 4,
@@ -14,10 +14,10 @@ export const SITE = {
editPost: {
enabled: true,
text: "Edit page",
url: "https://github.com/satnaing/astro-paper/edit/main/",
url: "https://github.com/hec-ia/wiki/edit/main/",
},
dynamicOgImage: true,
dir: "ltr", // "rtl" | "auto"
lang: "en", // html lang code. Set this empty and default will be "en"
timezone: "Asia/Bangkok", // Default global timezone (IANA format) https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
timezone: "Europe/Paris", // Default global timezone (IANA format) https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
} as const;

View File

@@ -3,6 +3,10 @@ import { glob } from "astro/loaders";
import { SITE } from "@/config";
export const BLOG_PATH = "src/data/blog";
export const EVENTS_PATH = "src/data/events";
export const WORKSHOPS_PATH = "src/data/workshops";
export const NEWS_PATH = "src/data/news";
export const TECHNICAL_PATH = "src/data/technical";
const blog = defineCollection({
loader: glob({ pattern: "**/[^_]*.md", base: `./${BLOG_PATH}` }),
@@ -23,4 +27,90 @@ const blog = defineCollection({
}),
});
export const collections = { blog };
const events = defineCollection({
loader: glob({ pattern: "**/[^_]*.md", base: `./${EVENTS_PATH}` }),
schema: ({ image }) =>
z.object({
author: z.string().default(SITE.author),
pubDatetime: z.date(),
eventDate: z.date(),
eventEndDate: z.date().optional(),
location: z.string().optional(),
modDatetime: z.date().optional().nullable(),
title: z.string(),
featured: z.boolean().optional(),
draft: z.boolean().optional(),
tags: z.array(z.string()).default(["event"]),
ogImage: image().or(z.string()).optional(),
description: z.string(),
canonicalURL: z.string().optional(),
hideEditPost: z.boolean().optional(),
timezone: z.string().optional(),
registrationLink: z.string().optional(),
}),
});
const workshops = defineCollection({
loader: glob({ pattern: "**/[^_]*.md", base: `./${WORKSHOPS_PATH}` }),
schema: ({ image }) =>
z.object({
author: z.string().default(SITE.author),
pubDatetime: z.date(),
workshopDate: z.date(),
duration: z.string().optional(),
level: z.enum(["beginner", "intermediate", "advanced"]).optional(),
modDatetime: z.date().optional().nullable(),
title: z.string(),
featured: z.boolean().optional(),
draft: z.boolean().optional(),
tags: z.array(z.string()).default(["workshop"]),
ogImage: image().or(z.string()).optional(),
description: z.string(),
canonicalURL: z.string().optional(),
hideEditPost: z.boolean().optional(),
timezone: z.string().optional(),
materials: z.string().optional(),
}),
});
const news = defineCollection({
loader: glob({ pattern: "**/[^_]*.md", base: `./${NEWS_PATH}` }),
schema: ({ image }) =>
z.object({
author: z.string().default(SITE.author),
pubDatetime: z.date(),
modDatetime: z.date().optional().nullable(),
title: z.string(),
featured: z.boolean().optional(),
draft: z.boolean().optional(),
tags: z.array(z.string()).default(["news"]),
ogImage: image().or(z.string()).optional(),
description: z.string(),
canonicalURL: z.string().optional(),
hideEditPost: z.boolean().optional(),
timezone: z.string().optional(),
}),
});
const technical = defineCollection({
loader: glob({ pattern: "**/[^_]*.md", base: `./${TECHNICAL_PATH}` }),
schema: ({ image }) =>
z.object({
author: z.string().default(SITE.author),
pubDatetime: z.date(),
modDatetime: z.date().optional().nullable(),
title: z.string(),
featured: z.boolean().optional(),
draft: z.boolean().optional(),
tags: z.array(z.string()).default(["technical"]),
ogImage: image().or(z.string()).optional(),
description: z.string(),
canonicalURL: z.string().optional(),
hideEditPost: z.boolean().optional(),
timezone: z.string().optional(),
difficulty: z.enum(["beginner", "intermediate", "advanced"]).optional(),
readingTime: z.string().optional(),
}),
});
export const collections = { blog, events, workshops, news, technical };

View File

@@ -16,8 +16,8 @@ import IconChevronRight from "@/assets/icons/IconChevronRight.svg";
import { SITE } from "@/config";
type Props = {
post: CollectionEntry<"blog">;
posts: CollectionEntry<"blog">[];
post: CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">;
posts: CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">[];
};
const { post, posts } = Astro.props;

View File

@@ -0,0 +1,32 @@
---
import type { GetStaticPaths } from "astro";
import { getCollection } from "astro:content";
import Main from "@/layouts/Main.astro";
import Layout from "@/layouts/Layout.astro";
import Header from "@/components/Header.astro";
import Footer from "@/components/Footer.astro";
import Card from "@/components/Card.astro";
import Pagination from "@/components/Pagination.astro";
import getSortedPosts from "@/utils/getSortedPosts";
import { SITE } from "@/config";
export const getStaticPaths = (async ({ paginate }) => {
const events = await getCollection("events", ({ data }) => !data.draft);
return paginate(getSortedPosts(events), { pageSize: SITE.postPerPage });
}) satisfies GetStaticPaths;
const { page } = Astro.props;
---
<Layout title={`Events | ${SITE.title}`}>
<Header />
<Main pageTitle="Events" pageDesc="All upcoming and past events organized by HEC IA.">
<ul>
{page.data.map(data => <Card {...data} />)}
</ul>
</Main>
<Pagination {page} />
<Footer noMarginTop={page.lastPage > 1} />
</Layout>

View File

@@ -0,0 +1,27 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import PostDetails from "@/layouts/PostDetails.astro";
import getSortedPosts from "@/utils/getSortedPosts";
import { getPath } from "@/utils/getPath";
type Props = {
post: CollectionEntry<"events">;
};
export async function getStaticPaths() {
const posts = await getCollection("events", ({ data }) => !data.draft);
const postResult = posts.map(post => ({
params: { slug: getPath(post.id, post.filePath, false) },
props: { post },
}));
return postResult;
}
const { post } = Astro.props;
const posts = await getCollection("events");
const sortedPosts = getSortedPosts(posts);
---
<PostDetails post={post} posts={sortedPosts} />

View File

@@ -12,11 +12,25 @@ import IconArrowRight from "@/assets/icons/IconArrowRight.svg";
import { SITE } from "@/config";
import { SOCIALS } from "@/constants";
const posts = await getCollection("blog");
const events = await getCollection("events");
const workshops = await getCollection("workshops");
const news = await getCollection("news");
const technical = await getCollection("technical");
const sortedPosts = getSortedPosts(posts);
const featuredPosts = sortedPosts.filter(({ data }) => data.featured);
const recentPosts = sortedPosts.filter(({ data }) => !data.featured);
const sortedEvents = getSortedPosts(events);
const sortedWorkshops = getSortedPosts(workshops);
const sortedNews = getSortedPosts(news);
const sortedTechnical = getSortedPosts(technical);
const featuredEvents = sortedEvents.filter(({ data }) => data.featured);
const featuredWorkshops = sortedWorkshops.filter(({ data }) => data.featured);
const featuredNews = sortedNews.filter(({ data }) => data.featured);
const featuredTechnical = sortedTechnical.filter(({ data }) => data.featured);
const recentEvents = sortedEvents.slice(0, SITE.postPerIndex);
const recentWorkshops = sortedWorkshops.slice(0, SITE.postPerIndex);
const recentNews = sortedNews.slice(0, SITE.postPerIndex);
const recentTechnical = sortedTechnical.slice(0, SITE.postPerIndex);
---
<Layout>
@@ -24,7 +38,7 @@ const recentPosts = sortedPosts.filter(({ data }) => !data.featured);
<main id="main-content" data-layout="index" class="app-layout">
<section id="hero" class:list={["pt-8 pb-6", "border-b border-border"]}>
<h1 class="my-4 inline-block text-4xl font-bold sm:my-8 sm:text-5xl">
Mingalaba
Welcome to HEC IA
</h1>
<a
target="_blank"
@@ -42,19 +56,13 @@ const recentPosts = sortedPosts.filter(({ data }) => !data.featured);
</a>
<p>
AstroPaper is a minimal, responsive, accessible and SEO-friendly Astro
blog theme. This theme follows best practices and provides accessibility
out of the box. Light and dark mode are supported by default. Moreover,
additional color schemes can also be configured.
At HEC IA we make AI and technology more accessible to tomorrow's
managers. Our goal, make people on the campus able to leverage AI to its
full potential.
</p>
<p class="mt-2">
Read the blog posts or check
<LinkButton
class="underline decoration-dashed underline-offset-4 hover:text-accent"
href="https://github.com/satnaing/astro-paper#readme"
>
README
</LinkButton> for more info.
Explore our events, workshops, latest news, and technical deep dives to
stay up-to-date with the latest in AI and in the tech world.
</p>
{
// only display if at least one social link is enabled
@@ -68,44 +76,111 @@ const recentPosts = sortedPosts.filter(({ data }) => !data.featured);
</section>
{
featuredPosts.length > 0 && (
(featuredEvents.length > 0 || recentEvents.length > 0) && (
<section
id="featured"
class:list={[
"pt-12 pb-6",
{ "border-b border-border": recentPosts.length > 0 },
]}
id="events"
class:list={["pt-12 pb-6", "border-b border-border"]}
>
<h2 class="text-2xl font-semibold tracking-wide">Featured</h2>
<h2 class="text-2xl font-semibold tracking-wide">Events</h2>
<ul>
{featuredPosts.map(data => (
{featuredEvents.map(data => (
<Card variant="h3" {...data} />
))}
{recentEvents
.slice(0, SITE.postPerIndex - featuredEvents.length)
.map(data => (
<Card variant="h3" {...data} />
))}
</ul>
<div class="my-4 text-center">
<LinkButton href="/events/1">
All Events
<IconArrowRight class="inline-block rtl:-rotate-180" />
</LinkButton>
</div>
</section>
)
}
{
recentPosts.length > 0 && (
<section id="recent-posts" class="pt-12 pb-6">
<h2 class="text-2xl font-semibold tracking-wide">Recent Posts</h2>
(featuredWorkshops.length > 0 || recentWorkshops.length > 0) && (
<section
id="workshops"
class:list={["pt-12 pb-6", "border-b border-border"]}
>
<h2 class="text-2xl font-semibold tracking-wide">Workshops</h2>
<ul>
{recentPosts.map(
(data, index) =>
index < SITE.postPerIndex && <Card variant="h3" {...data} />
)}
{featuredWorkshops.map(data => (
<Card variant="h3" {...data} />
))}
{recentWorkshops
.slice(0, SITE.postPerIndex - featuredWorkshops.length)
.map(data => (
<Card variant="h3" {...data} />
))}
</ul>
<div class="my-4 text-center">
<LinkButton href="/workshops/1">
All Workshops
<IconArrowRight class="inline-block rtl:-rotate-180" />
</LinkButton>
</div>
</section>
)
}
<div class="my-8 text-center">
<LinkButton href="/posts/">
All Posts
<IconArrowRight class="inline-block rtl:-rotate-180" />
</LinkButton>
</div>
{
(featuredNews.length > 0 || recentNews.length > 0) && (
<section
id="news"
class:list={["pt-12 pb-6", "border-b border-border"]}
>
<h2 class="text-2xl font-semibold tracking-wide">Latest News</h2>
<ul>
{featuredNews.map(data => (
<Card variant="h3" {...data} />
))}
{recentNews
.slice(0, SITE.postPerIndex - featuredNews.length)
.map(data => (
<Card variant="h3" {...data} />
))}
</ul>
<div class="my-4 text-center">
<LinkButton href="/news/1">
All News
<IconArrowRight class="inline-block rtl:-rotate-180" />
</LinkButton>
</div>
</section>
)
}
{
(featuredTechnical.length > 0 || recentTechnical.length > 0) && (
<section id="technical" class="pt-12 pb-6">
<h2 class="text-2xl font-semibold tracking-wide">
Technical Deep Dives
</h2>
<ul>
{featuredTechnical.map(data => (
<Card variant="h3" {...data} />
))}
{recentTechnical
.slice(0, SITE.postPerIndex - featuredTechnical.length)
.map(data => (
<Card variant="h3" {...data} />
))}
</ul>
<div class="my-4 text-center">
<LinkButton href="/technical/1">
All Technical Articles
<IconArrowRight class="inline-block rtl:-rotate-180" />
</LinkButton>
</div>
</section>
)
}
</main>
<Footer />
</Layout>

View File

@@ -0,0 +1,32 @@
---
import type { GetStaticPaths } from "astro";
import { getCollection } from "astro:content";
import Main from "@/layouts/Main.astro";
import Layout from "@/layouts/Layout.astro";
import Header from "@/components/Header.astro";
import Footer from "@/components/Footer.astro";
import Card from "@/components/Card.astro";
import Pagination from "@/components/Pagination.astro";
import getSortedPosts from "@/utils/getSortedPosts";
import { SITE } from "@/config";
export const getStaticPaths = (async ({ paginate }) => {
const news = await getCollection("news", ({ data }) => !data.draft);
return paginate(getSortedPosts(news), { pageSize: SITE.postPerPage });
}) satisfies GetStaticPaths;
const { page } = Astro.props;
---
<Layout title={`News | ${SITE.title}`}>
<Header />
<Main pageTitle="News" pageDesc="Latest news and updates from HEC IA.">
<ul>
{page.data.map(data => <Card {...data} />)}
</ul>
</Main>
<Pagination {page} />
<Footer noMarginTop={page.lastPage > 1} />
</Layout>

View File

@@ -0,0 +1,27 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import PostDetails from "@/layouts/PostDetails.astro";
import getSortedPosts from "@/utils/getSortedPosts";
import { getPath } from "@/utils/getPath";
type Props = {
post: CollectionEntry<"news">;
};
export async function getStaticPaths() {
const posts = await getCollection("news", ({ data }) => !data.draft);
const postResult = posts.map(post => ({
params: { slug: getPath(post.id, post.filePath, false) },
props: { post },
}));
return postResult;
}
const { post } = Astro.props;
const posts = await getCollection("news");
const sortedPosts = getSortedPosts(posts);
---
<PostDetails post={post} posts={sortedPosts} />

View File

@@ -0,0 +1,32 @@
---
import type { GetStaticPaths } from "astro";
import { getCollection } from "astro:content";
import Main from "@/layouts/Main.astro";
import Layout from "@/layouts/Layout.astro";
import Header from "@/components/Header.astro";
import Footer from "@/components/Footer.astro";
import Card from "@/components/Card.astro";
import Pagination from "@/components/Pagination.astro";
import getSortedPosts from "@/utils/getSortedPosts";
import { SITE } from "@/config";
export const getStaticPaths = (async ({ paginate }) => {
const technical = await getCollection("technical", ({ data }) => !data.draft);
return paginate(getSortedPosts(technical), { pageSize: SITE.postPerPage });
}) satisfies GetStaticPaths;
const { page } = Astro.props;
---
<Layout title={`Technical Deep Dives | ${SITE.title}`}>
<Header />
<Main pageTitle="Technical Deep Dives" pageDesc="In-depth technical articles on AI and machine learning.">
<ul>
{page.data.map(data => <Card {...data} />)}
</ul>
</Main>
<Pagination {page} />
<Footer noMarginTop={page.lastPage > 1} />
</Layout>

View File

@@ -0,0 +1,27 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import PostDetails from "@/layouts/PostDetails.astro";
import getSortedPosts from "@/utils/getSortedPosts";
import { getPath } from "@/utils/getPath";
type Props = {
post: CollectionEntry<"technical">;
};
export async function getStaticPaths() {
const posts = await getCollection("technical", ({ data }) => !data.draft);
const postResult = posts.map(post => ({
params: { slug: getPath(post.id, post.filePath, false) },
props: { post },
}));
return postResult;
}
const { post } = Astro.props;
const posts = await getCollection("technical");
const sortedPosts = getSortedPosts(posts);
---
<PostDetails post={post} posts={sortedPosts} />

View File

@@ -0,0 +1,32 @@
---
import type { GetStaticPaths } from "astro";
import { getCollection } from "astro:content";
import Main from "@/layouts/Main.astro";
import Layout from "@/layouts/Layout.astro";
import Header from "@/components/Header.astro";
import Footer from "@/components/Footer.astro";
import Card from "@/components/Card.astro";
import Pagination from "@/components/Pagination.astro";
import getSortedPosts from "@/utils/getSortedPosts";
import { SITE } from "@/config";
export const getStaticPaths = (async ({ paginate }) => {
const workshops = await getCollection("workshops", ({ data }) => !data.draft);
return paginate(getSortedPosts(workshops), { pageSize: SITE.postPerPage });
}) satisfies GetStaticPaths;
const { page } = Astro.props;
---
<Layout title={`Workshops | ${SITE.title}`}>
<Header />
<Main pageTitle="Workshops" pageDesc="Hands-on technical workshops on AI topics.">
<ul>
{page.data.map(data => <Card {...data} />)}
</ul>
</Main>
<Pagination {page} />
<Footer noMarginTop={page.lastPage > 1} />
</Layout>

View File

@@ -0,0 +1,27 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import PostDetails from "@/layouts/PostDetails.astro";
import getSortedPosts from "@/utils/getSortedPosts";
import { getPath } from "@/utils/getPath";
type Props = {
post: CollectionEntry<"workshops">;
};
export async function getStaticPaths() {
const posts = await getCollection("workshops", ({ data }) => !data.draft);
const postResult = posts.map(post => ({
params: { slug: getPath(post.id, post.filePath, false) },
props: { post },
}));
return postResult;
}
const { post } = Astro.props;
const posts = await getCollection("workshops");
const sortedPosts = getSortedPosts(posts);
---
<PostDetails post={post} posts={sortedPosts} />

View File

@@ -1,7 +1,11 @@
import type { CollectionEntry } from "astro:content";
import postFilter from "./postFilter";
const getSortedPosts = (posts: CollectionEntry<"blog">[]) => {
const getSortedPosts = <
T extends "blog" | "events" | "workshops" | "news" | "technical",
>(
posts: CollectionEntry<T>[]
) => {
return posts
.filter(postFilter)
.sort(

View File

@@ -1,7 +1,11 @@
import type { CollectionEntry } from "astro:content";
import { SITE } from "@/config";
const postFilter = ({ data }: CollectionEntry<"blog">) => {
const postFilter = <
T extends "blog" | "events" | "workshops" | "news" | "technical",
>({
data,
}: CollectionEntry<T>) => {
const isPublishTimePassed =
Date.now() >
new Date(data.pubDatetime).getTime() - SITE.scheduledPostMargin;