Compare commits
5 Commits
c29b05bff3
...
d9862eae65
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9862eae65 | ||
|
|
dd4f6a7b01 | ||
|
|
d953687389 | ||
|
|
96d5300d95 | ||
|
|
6971fc231d |
@@ -1,8 +1,27 @@
|
|||||||
# Ignore Astro files
|
# Development files
|
||||||
*.astro
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# Ignore node_modules directory
|
# Cache and temp files
|
||||||
|
.astro/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
# Ignore build output
|
# Build output (will be rebuilt in Docker)
|
||||||
dist/
|
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
|
||||||
46
.gitea/workflows/build-image.yml
Normal file
46
.gitea/workflows/build-image.yml
Normal 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 }}
|
||||||
11
.gitea/workflows/debug.yml
Normal file
11
.gitea/workflows/debug.yml
Normal 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
3
.gitignore
vendored
@@ -25,4 +25,5 @@ pnpm-debug.log*
|
|||||||
|
|
||||||
# pagefind
|
# pagefind
|
||||||
|
|
||||||
public/pagefind
|
public/pagefind
|
||||||
|
SETUP.md
|
||||||
|
|||||||
19
Dockerfile
19
Dockerfile
@@ -5,13 +5,32 @@ WORKDIR /app
|
|||||||
# Install pnpm
|
# Install pnpm
|
||||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
COPY package.json pnpm-lock.yaml ./
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy all source files (respecting .dockerignore)
|
||||||
COPY . .
|
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
|
RUN pnpm run build
|
||||||
|
|
||||||
# Runtime stage for serving the application
|
# Runtime stage for serving the application
|
||||||
FROM nginx:mainline-alpine-slim AS runtime
|
FROM nginx:mainline-alpine-slim AS runtime
|
||||||
|
|
||||||
|
# Copy the built site from dist directory
|
||||||
COPY --from=base /app/dist /usr/share/nginx/html
|
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
|
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
333
README.md
@@ -1,180 +1,211 @@
|
|||||||
# AstroPaper 📄
|
# HEC IA Wiki 🤖
|
||||||
|
|
||||||

|

|
||||||
[](https://www.figma.com/community/file/1356898632249991861)
|
|
||||||

|
|
||||||

|
|
||||||
[](https://conventionalcommits.org)
|
|
||||||
[](http://commitizen.github.io/cz-cli/)
|
|
||||||
|
|
||||||
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
|
- **Events**: Information about upcoming and past events
|
||||||
- [x] super fast performance
|
- **Workshops**: Hands-on technical workshops and tutorials
|
||||||
- [x] accessible (Keyboard/VoiceOver)
|
- **News**: Latest updates and announcements from HEC IA
|
||||||
- [x] responsive (mobile ~ desktops)
|
- **Technical Deep Dives**: In-depth technical articles on AI topics
|
||||||
- [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/))
|
|
||||||
|
|
||||||
_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">
|
- Node.js 18+
|
||||||
<a href="https://pagespeed.web.dev/report?url=https%3A%2F%2Fastro-paper.pages.dev%2F&form_factor=desktop">
|
- npm or pnpm
|
||||||
<img width="710" alt="AstroPaper Lighthouse Score" src="AstroPaper-lighthouse-score.svg">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## 🚀 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
|
```bash
|
||||||
/
|
/
|
||||||
├── public/
|
├── public/
|
||||||
│ ├── pagefind/ # auto-generated when build
|
│ └── assets/ # Static assets
|
||||||
│ ├── favicon.svg
|
|
||||||
│ └── astropaper-og.jpg
|
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── assets/
|
│ ├── assets/ # SVG icons and images
|
||||||
│ │ ├── icons/
|
│ ├── components/ # Astro components
|
||||||
│ │ └── images/
|
│ ├── data/ # Content collections
|
||||||
│ ├── components/
|
│ │ ├── events/
|
||||||
│ ├── data/
|
│ │ ├── workshops/
|
||||||
│ │ └── blog/
|
│ │ ├── news/
|
||||||
│ │ └── some-blog-posts.md
|
│ │ └── technical/
|
||||||
│ ├── layouts/
|
│ ├── layouts/ # Page layouts
|
||||||
│ ├── pages/
|
│ ├── pages/ # Route pages
|
||||||
│ ├── scripts/
|
│ ├── styles/ # Global styles
|
||||||
│ ├── styles/
|
│ ├── utils/ # Utility functions
|
||||||
│ ├── utils/
|
│ ├── config.ts # Site configuration
|
||||||
│ ├── config.ts
|
│ └── content.config.ts # Content collections config
|
||||||
│ ├── constants.ts
|
├── astro.config.ts
|
||||||
│ ├── content.config.ts
|
├── package.json
|
||||||
│ ├── env.d.ts
|
└── README.md
|
||||||
│ └── remark-collapse.d.ts
|
|
||||||
└── astro.config.ts
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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/)
|
## 📄 License
|
||||||
- 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/)
|
|
||||||
|
|
||||||
## 💻 Tech Stack
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||||
|
|
||||||
**Main Framework** - [Astro](https://astro.build/)
|
## 🙏 Acknowledgments
|
||||||
**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)
|
|
||||||
|
|
||||||
## 👨🏻💻 Running Locally
|
Built with:
|
||||||
|
|
||||||
You can start using this project locally by running the following command in your desired directory:
|
- [Astro](https://astro.build/) - The web framework for content-driven websites
|
||||||
|
- [AstroPaper](https://github.com/satnaing/astro-paper) - The base theme
|
||||||
```bash
|
- [Tailwind CSS](https://tailwindcss.com/) - For styling
|
||||||
# pnpm
|
- [TypeScript](https://www.typescriptlang.org/) - For type safety
|
||||||
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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Made with 🤍 by [Sat Naing](https://satnaing.dev) 👨🏻💻 and [contributors](https://github.com/satnaing/astro-paper/graphs/contributors).
|
Made with ❤️ by HEC IA
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Datetime from "./Datetime.astro";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
variant?: "h2" | "h3";
|
variant?: "h2" | "h3";
|
||||||
} & CollectionEntry<"blog">;
|
} & CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">;
|
||||||
|
|
||||||
const { variant: Heading = "h2", id, data, filePath } = Astro.props;
|
const { variant: Heading = "h2", id, data, filePath } = Astro.props;
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import IconEdit from "@/assets/icons/IconEdit.svg";
|
|||||||
import { SITE } from "@/config";
|
import { SITE } from "@/config";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
hideEditPost?: CollectionEntry<"blog">["data"]["hideEditPost"];
|
hideEditPost?: CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">["data"]["hideEditPost"];
|
||||||
class?: string;
|
class?: string;
|
||||||
post: CollectionEntry<"blog">;
|
post: CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { hideEditPost, post, class: className = "" } = Astro.props;
|
const { hideEditPost, post, class: className = "" } = Astro.props;
|
||||||
|
|||||||
@@ -72,13 +72,23 @@ const isActive = (path: string) => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<li class="col-span-2">
|
<li class="col-span-2">
|
||||||
<a href="/posts" class:list={{ "active-nav": isActive("/posts") }}>
|
<a href="/events" class:list={{ "active-nav": isActive("/events") }}>
|
||||||
Posts
|
Events
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="col-span-2">
|
<li class="col-span-2">
|
||||||
<a href="/tags" class:list={{ "active-nav": isActive("/tags") }}>
|
<a href="/workshops" class:list={{ "active-nav": isActive("/workshops") }}>
|
||||||
Tags
|
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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="col-span-2">
|
<li class="col-span-2">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import IconArrowRight from "@/assets/icons/IconArrowRight.svg";
|
|||||||
import LinkButton from "./LinkButton.astro";
|
import LinkButton from "./LinkButton.astro";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
page: Page<CollectionEntry<"blog">>;
|
page: Page<CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { page } = Astro.props;
|
const { page } = Astro.props;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
export const SITE = {
|
export const SITE = {
|
||||||
website: "https://astro-paper.pages.dev/", // replace this with your deployed domain
|
website: "https://wiki.hec-ia.com/", // replace this with your deployed domain
|
||||||
author: "Sat Naing",
|
author: "HEC IA",
|
||||||
profile: "https://satnaing.dev/",
|
profile: "https://wiki.hec-ia.com/about",
|
||||||
desc: "A minimal, responsive and SEO-friendly Astro blog theme.",
|
desc: "HEC IA student association wiki - Events, workshops, news, and technical deep dives on artificial intelligence.",
|
||||||
title: "AstroPaper",
|
title: "HEC IA Wiki",
|
||||||
ogImage: "astropaper-og.jpg",
|
ogImage: "hec-ia-og.jpg",
|
||||||
lightAndDarkMode: true,
|
lightAndDarkMode: true,
|
||||||
postPerIndex: 4,
|
postPerIndex: 4,
|
||||||
postPerPage: 4,
|
postPerPage: 4,
|
||||||
@@ -14,10 +14,10 @@ export const SITE = {
|
|||||||
editPost: {
|
editPost: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
text: "Edit page",
|
text: "Edit page",
|
||||||
url: "https://github.com/satnaing/astro-paper/edit/main/",
|
url: "https://github.com/hec-ia/wiki/edit/main/",
|
||||||
},
|
},
|
||||||
dynamicOgImage: true,
|
dynamicOgImage: true,
|
||||||
dir: "ltr", // "rtl" | "auto"
|
dir: "ltr", // "rtl" | "auto"
|
||||||
lang: "en", // html lang code. Set this empty and default will be "en"
|
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;
|
} as const;
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { glob } from "astro/loaders";
|
|||||||
import { SITE } from "@/config";
|
import { SITE } from "@/config";
|
||||||
|
|
||||||
export const BLOG_PATH = "src/data/blog";
|
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({
|
const blog = defineCollection({
|
||||||
loader: glob({ pattern: "**/[^_]*.md", base: `./${BLOG_PATH}` }),
|
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 };
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import IconChevronRight from "@/assets/icons/IconChevronRight.svg";
|
|||||||
import { SITE } from "@/config";
|
import { SITE } from "@/config";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
post: CollectionEntry<"blog">;
|
post: CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">;
|
||||||
posts: CollectionEntry<"blog">[];
|
posts: CollectionEntry<"blog" | "events" | "workshops" | "news" | "technical">[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const { post, posts } = Astro.props;
|
const { post, posts } = Astro.props;
|
||||||
|
|||||||
32
src/pages/events/[...page].astro
Normal file
32
src/pages/events/[...page].astro
Normal 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>
|
||||||
27
src/pages/events/[...slug]/index.astro
Normal file
27
src/pages/events/[...slug]/index.astro
Normal 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} />
|
||||||
@@ -12,11 +12,25 @@ import IconArrowRight from "@/assets/icons/IconArrowRight.svg";
|
|||||||
import { SITE } from "@/config";
|
import { SITE } from "@/config";
|
||||||
import { SOCIALS } from "@/constants";
|
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 sortedEvents = getSortedPosts(events);
|
||||||
const featuredPosts = sortedPosts.filter(({ data }) => data.featured);
|
const sortedWorkshops = getSortedPosts(workshops);
|
||||||
const recentPosts = sortedPosts.filter(({ data }) => !data.featured);
|
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>
|
<Layout>
|
||||||
@@ -24,7 +38,7 @@ const recentPosts = sortedPosts.filter(({ data }) => !data.featured);
|
|||||||
<main id="main-content" data-layout="index" class="app-layout">
|
<main id="main-content" data-layout="index" class="app-layout">
|
||||||
<section id="hero" class:list={["pt-8 pb-6", "border-b border-border"]}>
|
<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">
|
<h1 class="my-4 inline-block text-4xl font-bold sm:my-8 sm:text-5xl">
|
||||||
Mingalaba
|
Welcome to HEC IA
|
||||||
</h1>
|
</h1>
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -42,19 +56,13 @@ const recentPosts = sortedPosts.filter(({ data }) => !data.featured);
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
AstroPaper is a minimal, responsive, accessible and SEO-friendly Astro
|
At HEC IA we make AI and technology more accessible to tomorrow's
|
||||||
blog theme. This theme follows best practices and provides accessibility
|
managers. Our goal, make people on the campus able to leverage AI to its
|
||||||
out of the box. Light and dark mode are supported by default. Moreover,
|
full potential.
|
||||||
additional color schemes can also be configured.
|
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2">
|
<p class="mt-2">
|
||||||
Read the blog posts or check
|
Explore our events, workshops, latest news, and technical deep dives to
|
||||||
<LinkButton
|
stay up-to-date with the latest in AI and in the tech world.
|
||||||
class="underline decoration-dashed underline-offset-4 hover:text-accent"
|
|
||||||
href="https://github.com/satnaing/astro-paper#readme"
|
|
||||||
>
|
|
||||||
README
|
|
||||||
</LinkButton> for more info.
|
|
||||||
</p>
|
</p>
|
||||||
{
|
{
|
||||||
// only display if at least one social link is enabled
|
// only display if at least one social link is enabled
|
||||||
@@ -68,44 +76,111 @@ const recentPosts = sortedPosts.filter(({ data }) => !data.featured);
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{
|
{
|
||||||
featuredPosts.length > 0 && (
|
(featuredEvents.length > 0 || recentEvents.length > 0) && (
|
||||||
<section
|
<section
|
||||||
id="featured"
|
id="events"
|
||||||
class:list={[
|
class:list={["pt-12 pb-6", "border-b border-border"]}
|
||||||
"pt-12 pb-6",
|
|
||||||
{ "border-b border-border": recentPosts.length > 0 },
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<h2 class="text-2xl font-semibold tracking-wide">Featured</h2>
|
<h2 class="text-2xl font-semibold tracking-wide">Events</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{featuredPosts.map(data => (
|
{featuredEvents.map(data => (
|
||||||
<Card variant="h3" {...data} />
|
<Card variant="h3" {...data} />
|
||||||
))}
|
))}
|
||||||
|
{recentEvents
|
||||||
|
.slice(0, SITE.postPerIndex - featuredEvents.length)
|
||||||
|
.map(data => (
|
||||||
|
<Card variant="h3" {...data} />
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
<div class="my-4 text-center">
|
||||||
|
<LinkButton href="/events/1">
|
||||||
|
All Events
|
||||||
|
<IconArrowRight class="inline-block rtl:-rotate-180" />
|
||||||
|
</LinkButton>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
recentPosts.length > 0 && (
|
(featuredWorkshops.length > 0 || recentWorkshops.length > 0) && (
|
||||||
<section id="recent-posts" class="pt-12 pb-6">
|
<section
|
||||||
<h2 class="text-2xl font-semibold tracking-wide">Recent Posts</h2>
|
id="workshops"
|
||||||
|
class:list={["pt-12 pb-6", "border-b border-border"]}
|
||||||
|
>
|
||||||
|
<h2 class="text-2xl font-semibold tracking-wide">Workshops</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{recentPosts.map(
|
{featuredWorkshops.map(data => (
|
||||||
(data, index) =>
|
<Card variant="h3" {...data} />
|
||||||
index < SITE.postPerIndex && <Card variant="h3" {...data} />
|
))}
|
||||||
)}
|
{recentWorkshops
|
||||||
|
.slice(0, SITE.postPerIndex - featuredWorkshops.length)
|
||||||
|
.map(data => (
|
||||||
|
<Card variant="h3" {...data} />
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
<div class="my-4 text-center">
|
||||||
|
<LinkButton href="/workshops/1">
|
||||||
|
All Workshops
|
||||||
|
<IconArrowRight class="inline-block rtl:-rotate-180" />
|
||||||
|
</LinkButton>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="my-8 text-center">
|
{
|
||||||
<LinkButton href="/posts/">
|
(featuredNews.length > 0 || recentNews.length > 0) && (
|
||||||
All Posts
|
<section
|
||||||
<IconArrowRight class="inline-block rtl:-rotate-180" />
|
id="news"
|
||||||
</LinkButton>
|
class:list={["pt-12 pb-6", "border-b border-border"]}
|
||||||
</div>
|
>
|
||||||
|
<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>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
32
src/pages/news/[...page].astro
Normal file
32
src/pages/news/[...page].astro
Normal 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>
|
||||||
27
src/pages/news/[...slug]/index.astro
Normal file
27
src/pages/news/[...slug]/index.astro
Normal 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} />
|
||||||
32
src/pages/technical/[...page].astro
Normal file
32
src/pages/technical/[...page].astro
Normal 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>
|
||||||
27
src/pages/technical/[...slug]/index.astro
Normal file
27
src/pages/technical/[...slug]/index.astro
Normal 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} />
|
||||||
32
src/pages/workshops/[...page].astro
Normal file
32
src/pages/workshops/[...page].astro
Normal 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>
|
||||||
27
src/pages/workshops/[...slug]/index.astro
Normal file
27
src/pages/workshops/[...slug]/index.astro
Normal 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} />
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
import postFilter from "./postFilter";
|
import postFilter from "./postFilter";
|
||||||
|
|
||||||
const getSortedPosts = (posts: CollectionEntry<"blog">[]) => {
|
const getSortedPosts = <
|
||||||
|
T extends "blog" | "events" | "workshops" | "news" | "technical",
|
||||||
|
>(
|
||||||
|
posts: CollectionEntry<T>[]
|
||||||
|
) => {
|
||||||
return posts
|
return posts
|
||||||
.filter(postFilter)
|
.filter(postFilter)
|
||||||
.sort(
|
.sort(
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
import { SITE } from "@/config";
|
import { SITE } from "@/config";
|
||||||
|
|
||||||
const postFilter = ({ data }: CollectionEntry<"blog">) => {
|
const postFilter = <
|
||||||
|
T extends "blog" | "events" | "workshops" | "news" | "technical",
|
||||||
|
>({
|
||||||
|
data,
|
||||||
|
}: CollectionEntry<T>) => {
|
||||||
const isPublishTimePassed =
|
const isPublishTimePassed =
|
||||||
Date.now() >
|
Date.now() >
|
||||||
new Date(data.pubDatetime).getTime() - SITE.scheduledPostMargin;
|
new Date(data.pubDatetime).getTime() - SITE.scheduledPostMargin;
|
||||||
|
|||||||
Reference in New Issue
Block a user