refactor: rebase from hard fork into soft fork that follows upstream
35
.forgejo/workflows/build-dist.yaml
Normal file
|
@ -0,0 +1,35 @@
|
|||
name: build dist
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-dist:
|
||||
runs-on: docker
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup bun.sh
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
- name: Deploy to server
|
||||
uses: https://github.com/appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
source: "dist/*"
|
||||
target: ${{ secrets.TARGET }}
|
||||
strip_components: 1
|
||||
overwrite: true
|
35
.forgejo/workflows/update-dependencies.yaml
Normal file
|
@ -0,0 +1,35 @@
|
|||
name: update dependencies
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-deps:
|
||||
runs-on: docker
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup bun.sh
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Update dependencies
|
||||
run: |
|
||||
bun update
|
||||
bun install
|
||||
|
||||
- name: Commit updates
|
||||
run: |
|
||||
git config user.name "actions[bot]"
|
||||
git config user.email "actions[bot]@git.z0x.ca"
|
||||
git add .
|
||||
if git diff --staged --quiet; then
|
||||
echo "No updates avalable"
|
||||
else
|
||||
git commit -m "chore(deps): bump dependencies"
|
||||
git push
|
||||
fi
|
4
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
11
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"conventionalCommits.scopes": ["workflow", "index", "package", "content"]
|
||||
}
|
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Trevor Lee
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
278
README.md
|
@ -1,278 +0,0 @@
|
|||

|
||||
|
||||
<div align="center">
|
||||
|
||||
## astro-erudite
|
||||
|
||||
![Stargazers]
|
||||
[![License]](LICENSE)
|
||||
|
||||
</div>
|
||||
|
||||
astro-erudite is an opinionated, unstyled static blogging template built with [Astro](https://astro.build/), [Tailwind](https://tailwindcss.com/), and [shadcn/ui](https://ui.shadcn.com/). Extraordinarily loosely based off the [Astro Micro](https://astro-micro.vercel.app/) theme by [trevortylerlee](https://github.com/trevortylerlee).
|
||||
|
||||
> [!NOTE]
|
||||
> To learn more about why this template exists, read [The State of Static Blogs in 2024](https://astro-erudite.vercel.app/blog/the-state-of-static-blogs), where I share my take on what constitutes a great blogging template and my goals while developing this one.
|
||||
|
||||
---
|
||||
|
||||
## Community Examples
|
||||
|
||||
Below are some fantastic examples of websites based on this template. If you wish to add your site to this list, open a pull request!
|
||||
|
||||
| Site | Author | Description/Features | Source |
|
||||
| --------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
||||
| [enscribe.dev](https://enscribe.dev) | [@jktrn](https://github.com/jktrn) | Heavily modified bento-style homepage with client interactivity, with custom MDX components! | [→](https://github.com/jktrn/enscribe.dev) |
|
||||
| [emile.sh](https://emile.sh) | [@echoghi](https://github.com/echoghi) | A minimalist personal blog using the [flexoki](https://stephango.com/flexoki) theme | [→](https://github.com/echoghi/v5) |
|
||||
| [decentparadox.me](https://decentparadox.me) | [@decentparadox](https://github.com/decentparadox) | A heavily customized personal portfolio with a sci-fi theme! | [→](https://github.com/decentparadox/decentparadox.me) |
|
||||
| [flocto.github.io](https://flocto.github.io/) | [@flocto](https://github.com/flocto) | A slightly modified personal blog | [→](https://github.com/flocto/flocto.github.io) |
|
||||
| [dumbprism.me](https://www.dumbprism.me/) | [@dumbprism](https://github.com/dumbprism) | A customized portfolio inspired by enscribe's bento grid style adding my gist of UI | [→](https://github.com/dumbprism/dumbprism-portfolio) |
|
||||
| [hyuki.dev](https://hyuki.dev/) | [@snow0406](https://github.com/snow0406) | A minimalist blog with a blue color scheme, focusing on simplicity! | [→](https://github.com/Snow0406/hyuki.dev) |
|
||||
|
||||
## Features
|
||||
|
||||
- [Astro](https://astro.build/)’s [Islands](https://docs.astro.build/en/concepts/islands/) architecture for partial/selective hydration and client-side interactivity while maintaining a fast-to-render static site.
|
||||
- [shadcn/ui](https://ui.shadcn.com/)’s [Tailwind](https://tailwindcss.com/) color convention for automatic styling across both light and dark themes. Includes accessible, theme-aware UI components for navigation, buttons, etc.
|
||||
- [Expressive Code](https://expressive-code.com/) for advanced code block styling, highlighting, and code block titles/captions.
|
||||
- Blog post authoring using [MDX](https://mdxjs.com/) for component-style content, alongside $\LaTeX$ rendering using [KaTeX](https://katex.org/).
|
||||
- Astro [View Transitions](https://docs.astro.build/en/guides/view-transitions/) in <abbr title="Single Page Application">SPA</abbr> mode for smooth, opt-in animations during route switching.
|
||||
- SEO optimization with fine-grained control over metadata and [Open Graph](https://ogp.me/) tags for each post.
|
||||
- [RSS](https://en.wikipedia.org/wiki/RSS) feeds and sitemap generation!
|
||||
- Supports author profiles (with a dedicated authors page) and adding multiple authors per post.
|
||||
- Supports project tags (with a dedicated tags page) for easy post categorization and discovery.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
This is a list of the various technologies used to build this template:
|
||||
|
||||
| Category | Technology Name |
|
||||
| ---------- | ------------------------------------------------------------------------------------------ |
|
||||
| Framework | [Astro](https://astro.build/) |
|
||||
| Styling | [Tailwind](https://tailwindcss.com) |
|
||||
| Components | [shadcn/ui](https://ui.shadcn.com/) |
|
||||
| Content | [MDX](https://mdxjs.com/) |
|
||||
| Codeblocks | [Expressive Code](https://expressive-code.com/), [Shiki](https://github.com/shikijs/shiki) |
|
||||
| Graphics | [Figma](https://www.figma.com/) |
|
||||
| Deployment | [Vercel](https://vercel.com) |
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Hit “Use this template”, the big green button on the top right, to create a new repository in your own GitHub account with this template.
|
||||
|
||||
2. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/[YOUR_USERNAME]/[YOUR_REPO_NAME].git
|
||||
cd [YOUR_REPO_NAME]
|
||||
```
|
||||
|
||||
3. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
4. Start the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
5. Open your browser and visit `http://localhost:1234` to get started. The following commands are also available:
|
||||
|
||||
| Command | Description |
|
||||
| ------------------ | --------------------------------------------------------------- |
|
||||
| `npm run start` | Alias for `npm run dev` |
|
||||
| `npm run build` | Run type checking and build the project |
|
||||
| `npm run preview` | Previews the built project |
|
||||
| `npm run astro` | Run Astro CLI commands |
|
||||
| `npm run prettier` | Blanket format all files using [Prettier](https://prettier.io/) |
|
||||
|
||||
## Customization
|
||||
|
||||
### Site Configuration
|
||||
|
||||
Edit the `src/consts.ts` file to update your site's metadata, navigation links, and social links:
|
||||
|
||||
```ts
|
||||
export const SITE: Site = {
|
||||
title: 'astro-erudite',
|
||||
description:
|
||||
'astro-erudite is a opinionated, unstyled blogging template—built with Astro, Tailwind, and shadcn/ui.',
|
||||
href: 'https://astro-erudite.vercel.app',
|
||||
featuredPostCount: 2,
|
||||
postsPerPage: 3,
|
||||
}
|
||||
|
||||
export const NAV_LINKS: SocialLink[] = [
|
||||
{
|
||||
href: '/blog',
|
||||
label: 'blog',
|
||||
},
|
||||
// ...
|
||||
]
|
||||
|
||||
export const SOCIAL_LINKS: SocialLink[] = [
|
||||
{
|
||||
href: 'https://github.com/jktrn',
|
||||
label: 'GitHub',
|
||||
},
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
### Color Palette
|
||||
|
||||
Colors are defined in `src/styles/global.css` in [OKLCH format](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch), using the [shadcn/ui](https://ui.shadcn.com/) convention:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
### Favicons
|
||||
|
||||
Favicons are generated using [RealFaviconGenerator](https://realfavicongenerator.net/). To adjust the favicons, replace the files in the `public/` directory (such as `favicon.ico`, `favicon.svg`, `apple-touch-icon.png`, etc.) with your own. After updating the favicon files, you'll also need to adjust the references in `src/components/Head.astro` to match your new favicon filenames and paths:
|
||||
|
||||
```html
|
||||
<!-- Replace these with the generated meta tags -->
|
||||
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<meta name="apple-mobile-web-app-title" content="astro-erudite" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
```
|
||||
|
||||
## Adding Content
|
||||
|
||||
### Blog Posts
|
||||
|
||||
Add new blog posts as MDX files in the `src/content/blog/` directory. Use the following frontmatter structure:
|
||||
|
||||
```yml
|
||||
---
|
||||
title: 'Your Post Title'
|
||||
description: 'A brief description of your post!'
|
||||
date: 2024-01-01
|
||||
tags: ['tag1', 'tag2']
|
||||
image: './image.png'
|
||||
authors: ['author1', 'author2']
|
||||
draft: false
|
||||
---
|
||||
```
|
||||
|
||||
The blog post schema is defined as follows:
|
||||
|
||||
| Field | Type (Zod) | Requirements | Required |
|
||||
| ------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
||||
| `title` | `string` | Should be ≤60 characters. | Yes |
|
||||
| `description` | `string` | Should be ≤155 characters. | Yes |
|
||||
| `date` | `coerce.date()` | Must be in `YYYY-MM-DD` format. | Yes |
|
||||
| `image` | `image()` | Should be exactly 1200px × 630px. | Optional |
|
||||
| `tags` | `string[]` | Preferably use kebab-case for these. | Optional |
|
||||
| `authors` | `string[]` | If the author has a profile, use the id associated with their Markdown file in `src/content/authors/` (e.g. if their file is named `jane-doe.md`, use `jane-doe` in the array). | Optional |
|
||||
| `draft` | `boolean` | Defaults to `false` if not provided. | Optional |
|
||||
|
||||
### Authors
|
||||
|
||||
Add author information in `src/content/authors/` as Markdown files. A file named `[author-name].md` can be associated with a blog post if `"author-name"` (the id) is added to the `authors` field:
|
||||
|
||||
```yml
|
||||
---
|
||||
name: 'enscribe'
|
||||
pronouns: 'he/him'
|
||||
avatar: 'https://gravatar.com/avatar/9bfdc4ec972793cf05cb91efce5f4aaaec2a0da1bf4ec34dad0913f1d845faf6.webp?size=256'
|
||||
bio: 'd(-_-)b'
|
||||
website: 'https://enscribe.dev'
|
||||
twitter: 'https://twitter.com/enscry'
|
||||
github: 'https://github.com/jktrn'
|
||||
mail: 'jason@enscribe.dev'
|
||||
---
|
||||
```
|
||||
|
||||
The author schema is defined as follows:
|
||||
|
||||
| Field | Type (Zod) | Requirements | Required |
|
||||
| ---------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
|
||||
| `name` | `string` | n/a | Yes |
|
||||
| `pronouns` | `string` | n/a | Optional |
|
||||
| `avatar` | `string.url()` or `string.startsWith('/')` | Should be either a valid URL or a path starting with `/`. Preferably use [Gravatar](https://en.gravatar.com/site/implement/images/) with the `?size=256` size parameter. | Yes |
|
||||
| `bio` | `string` | n/a | Optional |
|
||||
| `mail` | `string.email()` | Must be a valid email address. | Optional |
|
||||
| `website` | `string.url()` | Must be a valid URL. | Optional |
|
||||
| `twitter` | `string.url()` | Must be a valid URL. | Optional |
|
||||
| `github` | `string.url()` | Must be a valid URL. | Optional |
|
||||
| `linkedin` | `string.url()` | Must be a valid URL. | Optional |
|
||||
| `discord` | `string.url()` | Must be a valid URL. | Optional |
|
||||
|
||||
> [!TIP]
|
||||
> You can add as many social media links as you want, as long as you adjust the schema! Make sure you also support the new field in the `src/components/SocialIcons.astro` component.
|
||||
|
||||
### Projects
|
||||
|
||||
Add projects in `src/content/projects/` as Markdown files:
|
||||
|
||||
```yml
|
||||
---
|
||||
name: 'Project A'
|
||||
description: 'This is an example project description! You should replace this with a description of your own project.'
|
||||
tags: ['Framework A', 'Library B', 'Tool C', 'Resource D']
|
||||
image: '/static/1200x630.png'
|
||||
link: 'https://example.com'
|
||||
---
|
||||
```
|
||||
|
||||
The project schema is defined as follows:
|
||||
|
||||
| Field | Type (Zod) | Requirements | Required |
|
||||
| ------------- | -------------- | --------------------------------------- | -------- |
|
||||
| `name` | `string` | n/a | Yes |
|
||||
| `description` | `string` | n/a | Yes |
|
||||
| `tags` | `string[]` | n/a | Yes |
|
||||
| `image` | `image()` | Should be exactly 1200px × 630px. | Yes |
|
||||
| `link` | `string.url()` | Must be a valid URL. | Yes |
|
||||
|
||||
## License
|
||||
|
||||
This project is open source and available under the [MIT License](LICENSE).
|
||||
|
||||
---
|
||||
|
||||
### Star History
|
||||
|
||||
<a href="https://star-history.com/#jktrn/astro-erudite&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=jktrn/astro-erudite&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=jktrn/astro-erudite&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=jktrn/astro-erudite&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
Built with ♥ by [enscribe](https://enscribe.dev)!
|
||||
|
||||
[Stargazers]: https://img.shields.io/github/stars/jktrn/astro-erudite?color=fafafa&logo=github&logoColor=fff&style=for-the-badge
|
||||
[License]: https://img.shields.io/github/license/jktrn/astro-erudite?color=0a0a0a&logo=github&logoColor=fff&style=for-the-badge
|
|
@ -19,9 +19,10 @@ import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-s
|
|||
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers'
|
||||
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import umami from "@yeskunall/astro-umami";
|
||||
|
||||
export default defineConfig({
|
||||
site: 'https://astro-erudite.vercel.app',
|
||||
site: 'https://blog.z0x.ca',
|
||||
integrations: [
|
||||
expressiveCode({
|
||||
themes: ['github-light', 'github-dark'],
|
||||
|
@ -33,9 +34,9 @@ export default defineConfig({
|
|||
collapseStyle: 'collapsible-auto',
|
||||
overridesByLang: {
|
||||
'ansi,bat,bash,batch,cmd,console,powershell,ps,ps1,psd1,psm1,sh,shell,shellscript,shellsession,text,zsh':
|
||||
{
|
||||
showLineNumbers: false,
|
||||
},
|
||||
{
|
||||
showLineNumbers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
styleOverrides: {
|
||||
|
@ -68,12 +69,15 @@ export default defineConfig({
|
|||
react(),
|
||||
sitemap(),
|
||||
icon(),
|
||||
umami({
|
||||
id: "b691181e-cad7-4c23-b16a-709872a0a7ab",
|
||||
endpointUrl: "https://umami.z0x.ca",
|
||||
}),
|
||||
],
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
server: {
|
||||
port: 1234,
|
||||
host: true,
|
||||
},
|
||||
devToolbar: {
|
||||
|
|
10329
package-lock.json
generated
|
@ -6,14 +6,13 @@
|
|||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro check && astro build",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"prettier": "prettier --write .",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@astrojs/markdown-remark": "^6.3.1",
|
||||
"@astrojs/mdx": "^4.2.1",
|
||||
"@astrojs/react": "^4.2.1",
|
||||
|
@ -21,6 +20,9 @@
|
|||
"@astrojs/sitemap": "^3.3.0",
|
||||
"@expressive-code/plugin-collapsible-sections": "^0.40.2",
|
||||
"@expressive-code/plugin-line-numbers": "^0.40.2",
|
||||
"@fontsource-variable/geist": "^5.2.5",
|
||||
"@fontsource-variable/geist-mono": "^5.2.5",
|
||||
"@fontsource/geist-mono": "^5.2.5",
|
||||
"@iconify-json/lucide": "^1.2.26",
|
||||
"@radix-ui/react-avatar": "^1.1.3",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||
|
@ -32,6 +34,7 @@
|
|||
"@tailwindcss/vite": "^4.0.7",
|
||||
"@types/react": "19.0.0",
|
||||
"@types/react-dom": "19.0.0",
|
||||
"@yeskunall/astro-umami": "^0.0.4",
|
||||
"astro": "^5.5.4",
|
||||
"astro-expressive-code": "^0.40.2",
|
||||
"astro-icon": "^1.1.5",
|
||||
|
@ -76,4 +79,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 4.5 KiB |
BIN
public/favicon-16x16.png
Normal file
After Width: | Height: | Size: 387 B |
BIN
public/favicon-32x32.png
Normal file
After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 511 B |
Before Width: | Height: | Size: 15 KiB |
|
@ -1,3 +1,68 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" width="512" height="512" viewBox="0 0 512 512"><image width="512" height="512" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAh3SURBVHgB7dgBccNAEMDA+04gPX8Ex8lFkACwdlFodHb3GQAg5W8AgBwBAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgKDP8NO99wwAvIwDAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABA0NndZwCAFAcAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAILOAMAL7e4zfOUAAECQAACAIAEAAEECAACCBAAABAkAAAgSAAAQJAAAIEgAAECQAACAIAEAAEECAACCBAAABAkAAAgSAAAQJAAAIEgAAECQAACAIAEAAEECAACCBAAABAkAAAgSAAAQJAAAIEgAAECQAACAIAEAAEECAACCBAAABAkAAAgSAAAQJAAAIEgAAECQAACAIAEAAEECAACCBAAABAkAAAgSAAAQJAAAIEgAAECQAACAIAEAAEECAACCBAAABAkAAAgSAAAQJAAAIEgAAECQAACAIAEAAEECAACCBAAABAkAAAgSAAAQJAAAIEgAAECQAACAIAEAAEECAACCBAAABAkAAAgSAAAQJAAAIEgAAECQAACAIAEAAEECAACCBAAABAkAAAgSAAAQdHb3GQAgxQEAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgKDP8NO99wwAvIwDAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABAkAAAgCABAABBAgAAggQAAAQJAAAIEgAAECQAACBIAABA0NndZwCAFAcAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIIEAAAECQAACBIAABAkAAAgSAAAQJAAAIAgAQAAQQIAAIL+Acz4FinZF2V3AAAAAElFTkSuQmCC"></image><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
|
||||
@media (prefers-color-scheme: dark) { :root { filter: none; } }
|
||||
</style></svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 256 256"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
inkscape:export-filename="dev/websites/z0x.ca_shadcn/src/assets/logo.svg"
|
||||
inkscape:export-xdpi="192"
|
||||
inkscape:export-ydpi="192"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="2.0802432"
|
||||
inkscape:cx="139.88749"
|
||||
inkscape:cy="111.04471"
|
||||
inkscape:window-width="1402"
|
||||
inkscape:window-height="892"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" /><defs
|
||||
id="defs1"><linearGradient
|
||||
id="linearGradient10"><stop
|
||||
style="stop-color:#f5f5f5;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop10" /><stop
|
||||
style="stop-color:#0a0a0a;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop11" /></linearGradient><radialGradient
|
||||
xlink:href="#linearGradient10"
|
||||
id="radialGradient15"
|
||||
cx="128.00018"
|
||||
cy="128.00395"
|
||||
fx="128.00018"
|
||||
fy="128.00395"
|
||||
r="73.343147"
|
||||
gradientUnits="userSpaceOnUse" /></defs><rect
|
||||
style="fill:#0a0a0a;fill-opacity:1;stroke-width:1.96924;stroke-linejoin:round"
|
||||
id="bg"
|
||||
width="256"
|
||||
height="256"
|
||||
x="0"
|
||||
y="0"
|
||||
ry="16" /><path
|
||||
style="display:inline;fill:url(#radialGradient15);stroke-width:1.18365"
|
||||
d="M 55.824663,125.40476 125.21246,55.832186 a 3.9865567,3.9865567 0.02025256 0 1 5.64732,0.002 l 69.31756,69.600498 a 4.0005896,4.0005896 90.112473 0 1 -0.0111,5.65726 l -69.34652,69.08498 a 4.0135339,4.0135339 0.01148544 0 1 -5.6664,-0.001 L 55.832634,131.06116 a 3.9993654,3.9993654 89.919265 0 1 -0.008,-5.6564 z M 125.77344,89.300192 89.179269,125.4342 a 3.9542778,3.9542778 90.033181 0 0 -0.0033,5.62424 l 36.551738,36.17581 a 4.0462516,4.0462516 0.03318674 0 0 5.68928,0.003 l 36.59418,-36.13401 a 3.9542766,3.9542766 90.033183 0 0 0.003,-5.62424 L 131.46272,89.303486 a 4.0462513,4.0462513 0.03317792 0 0 -5.68928,-0.0033 z"
|
||||
id="blur"
|
||||
transform="matrix(1.0225904,0,0,1.0225904,4.1082412,0.10439132)" /><path
|
||||
style="display:inline;fill:#171717;fill-opacity:1;stroke:none;stroke-width:1.18326947;stroke-opacity:1;stroke-dasharray:none"
|
||||
d="M 55.824663,125.40476 125.21246,55.832186 a 3.9865567,3.9865567 0.02025256 0 1 5.64732,0.002 l 69.31756,69.600498 a 4.0005896,4.0005896 90.112473 0 1 -0.0111,5.65726 l -69.34652,69.08498 a 4.0135339,4.0135339 0.01148544 0 1 -5.6664,-0.001 L 55.832634,131.06116 a 3.9993654,3.9993654 89.919265 0 1 -0.008,-5.6564 z M 125.77344,89.300192 89.179269,125.4342 a 3.9542778,3.9542778 90.033181 0 0 -0.0033,5.62424 l 36.551738,36.17581 a 4.0462516,4.0462516 0.03318674 0 0 5.68928,0.003 l 36.59418,-36.13401 a 3.9542766,3.9542766 90.033183 0 0 0.003,-5.62424 L 131.46272,89.303486 a 4.0462513,4.0462513 0.03317792 0 0 -5.68928,-0.0033 z"
|
||||
id="diamond"
|
||||
transform="matrix(1.0225904,0,0,1.0225904,-2.8917572,-2.895611)" /></svg>
|
||||
|
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.5 KiB |
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"name": "astro-erudite",
|
||||
"short_name": "astro-erudite",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/web-app-manifest-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/web-app-manifest-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#000000",
|
||||
"display": "standalone"
|
||||
}
|
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 2.2 KiB |
|
@ -1,16 +0,0 @@
|
|||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_271_118)">
|
||||
<rect width="512" height="74" fill="#CCCCCC"/>
|
||||
<rect y="146" width="512" height="74" fill="#CCCCCC"/>
|
||||
<rect y="292" width="512" height="74" fill="#CCCCCC"/>
|
||||
<rect y="438" width="512" height="74" fill="#CCCCCC"/>
|
||||
<rect y="74" width="72" height="72" fill="#CCCCCC"/>
|
||||
<rect y="366" width="72" height="72" fill="#CCCCCC"/>
|
||||
<rect x="440" y="220" width="72" height="72" fill="#CCCCCC"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_271_118">
|
||||
<rect width="512" height="512" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 632 B |
Before Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 8 KiB |
|
@ -1,70 +0,0 @@
|
|||
---
|
||||
import Link from '@/components/Link.astro'
|
||||
import AvatarComponent from '@/components/ui/avatar'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { SocialLink } from '@/types'
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
import SocialIcons from './SocialIcons.astro'
|
||||
|
||||
type Props = {
|
||||
author: CollectionEntry<'authors'>
|
||||
linkDisabled?: boolean
|
||||
}
|
||||
const { author, linkDisabled = false } = Astro.props
|
||||
const {
|
||||
name,
|
||||
avatar,
|
||||
bio,
|
||||
pronouns,
|
||||
github,
|
||||
twitter,
|
||||
linkedin,
|
||||
website,
|
||||
mail,
|
||||
} = author.data
|
||||
|
||||
const socialLinks: SocialLink[] = [
|
||||
website && { href: website, label: 'Website' },
|
||||
github && { href: github, label: 'GitHub' },
|
||||
twitter && { href: twitter, label: 'Twitter' },
|
||||
linkedin && { href: linkedin, label: 'LinkedIn' },
|
||||
mail && { href: `mailto:${mail}`, label: 'Email' },
|
||||
].filter(Boolean) as SocialLink[]
|
||||
---
|
||||
|
||||
<div
|
||||
class="has-[a:hover]:bg-secondary/50 overflow-hidden rounded-xl border p-4 transition-colors duration-300 ease-in-out"
|
||||
>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href={`/authors/${author.id}`}
|
||||
class={cn('block', linkDisabled && 'pointer-events-none')}
|
||||
>
|
||||
<AvatarComponent
|
||||
client:load
|
||||
src={avatar}
|
||||
alt={`Avatar of ${name}`}
|
||||
fallback={name[0]}
|
||||
className={cn(
|
||||
'size-32 rounded-md',
|
||||
!linkDisabled &&
|
||||
'hover:ring-primary transition-shadow duration-300 hover:cursor-pointer hover:ring-2',
|
||||
)}
|
||||
/>
|
||||
</Link>
|
||||
<div class="flex grow flex-col justify-between gap-y-4">
|
||||
<div>
|
||||
<div class="flex flex-wrap items-center gap-x-2">
|
||||
<h3 class="text-lg font-medium">{name}</h3>
|
||||
{
|
||||
pronouns && (
|
||||
<span class="text-muted-foreground text-sm">({pronouns})</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<p class="text-muted-foreground text-sm">{bio}</p>
|
||||
</div>
|
||||
<SocialIcons links={socialLinks} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,10 +1,6 @@
|
|||
---
|
||||
import AvatarComponent from '@/components/ui/avatar'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { parseAuthors } from '@/lib/server-utils'
|
||||
import { formatDate, readingTime } from '@/lib/utils'
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import { Image } from 'astro:assets'
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
import Link from './Link.astro'
|
||||
|
@ -19,19 +15,15 @@ const { entry } = Astro.props as {
|
|||
|
||||
const formattedDate = formatDate(entry.data.date)
|
||||
const readTime = readingTime(entry.body!)
|
||||
const authors = await parseAuthors(entry.data.authors ?? [])
|
||||
---
|
||||
|
||||
<div
|
||||
class="hover:bg-secondary/50 rounded-xl border p-4 transition-colors duration-300 ease-in-out"
|
||||
class="not-prose hover:bg-secondary/50 rounded-xl border p-4 transition-colors duration-300 ease-in-out"
|
||||
>
|
||||
<Link
|
||||
href={`/${entry.collection}/${entry.id}`}
|
||||
class="flex flex-col gap-4 sm:flex-row"
|
||||
>
|
||||
<Link href={`/blog/${entry.id}`} class="flex flex-col gap-4 sm:flex-row">
|
||||
{
|
||||
entry.data.image && (
|
||||
<div class="max-w-[200px] sm:shrink-0">
|
||||
<div class="max-w-[200px] sm:flex-shrink-0">
|
||||
<Image
|
||||
src={entry.data.image}
|
||||
alt={entry.data.title}
|
||||
|
@ -42,8 +34,8 @@ const authors = await parseAuthors(entry.data.authors ?? [])
|
|||
</div>
|
||||
)
|
||||
}
|
||||
<div class="grow">
|
||||
<h3 class="mb-1 text-lg font-medium">
|
||||
<div class="flex-grow">
|
||||
<h3 class="mb-1 text-lg font-semibold">
|
||||
{entry.data.title}
|
||||
</h3>
|
||||
<p class="text-muted-foreground mb-2 text-sm">
|
||||
|
@ -52,41 +44,10 @@ const authors = await parseAuthors(entry.data.authors ?? [])
|
|||
<div
|
||||
class="text-muted-foreground mb-2 flex flex-wrap items-center gap-x-2 text-xs"
|
||||
>
|
||||
{
|
||||
authors.length > 0 && (
|
||||
<>
|
||||
{authors.map((author) => (
|
||||
<div class="flex items-center gap-x-1.5">
|
||||
<AvatarComponent
|
||||
client:load
|
||||
src={author.avatar}
|
||||
alt={author.name}
|
||||
fallback={author.name[0]}
|
||||
className="size-5 rounded-full"
|
||||
/>
|
||||
<span>{author.name}</span>
|
||||
</div>
|
||||
))}
|
||||
<Separator orientation="vertical" className="h-4!" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
<span>{formattedDate}</span>
|
||||
<Separator orientation="vertical" className="h-4!" />
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
<span>{readTime}</span>
|
||||
</div>
|
||||
{
|
||||
entry.data.tags && (
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{entry.data.tags.map((tag) => (
|
||||
<Badge variant="secondary" className="flex items-center gap-x-1">
|
||||
<Icon name="lucide:hash" class="size-3" />
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import Container from '@/components/Container.astro'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { SOCIAL_LINKS } from '@/consts'
|
||||
import Link from './Link.astro'
|
||||
import SocialIcons from './SocialIcons.astro'
|
||||
---
|
||||
|
||||
|
@ -13,16 +12,11 @@ import SocialIcons from './SocialIcons.astro'
|
|||
>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<span class="text-muted-foreground text-center text-sm">
|
||||
© {new Date().getFullYear()} All rights reserved.
|
||||
© {new Date().getFullYear()} • z0x
|
||||
</span>
|
||||
<Separator orientation="vertical" className="h-4!" />
|
||||
<p class="text-muted-foreground text-center text-sm">
|
||||
Made with 🤍 by <Link
|
||||
href="https://github.com/jktrn"
|
||||
class="text-foreground"
|
||||
external
|
||||
underline>enscribe</Link
|
||||
>!
|
||||
All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
<SocialIcons links={SOCIAL_LINKS} />
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
---
|
||||
import '@fontsource-variable/geist'
|
||||
import '@fontsource-variable/geist-mono'
|
||||
import '../styles/global.css'
|
||||
import '../styles/typography.css'
|
||||
|
||||
|
@ -22,38 +24,48 @@ const { title, description, image = '/static/twitter-card.png' } = Astro.props
|
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no" />
|
||||
<meta
|
||||
name="format-detection"
|
||||
content="telephone=no,date=no,address=no,email=no,url=no"
|
||||
/>
|
||||
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
|
||||
<title>{title}</title>
|
||||
<meta name="title" content={title} />
|
||||
<meta name="apple-mobile-web-app-title" content={title} />
|
||||
|
||||
<meta name="description" content={description} />
|
||||
<meta name="author" content={SITE.title} />
|
||||
|
||||
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<meta name="apple-mobile-web-app-title" content="astro-erudite" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<meta name="theme-color" content="#121212" media="(prefers-color-scheme: dark)" />
|
||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
|
||||
<meta
|
||||
name="theme-color"
|
||||
content="#121212"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
<meta
|
||||
name="theme-color"
|
||||
content="#ffffff"
|
||||
media="(prefers-color-scheme: light)"
|
||||
/>
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
<meta property="og:site_name" content={SITE.title} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:image" content={new URL(image, Astro.url)} />
|
||||
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={Astro.url} />
|
||||
<meta property="twitter:title" content={title} />
|
||||
<meta property="twitter:description" content={description} />
|
||||
<meta property="twitter:image" content={new URL(image, Astro.url)} />
|
||||
|
||||
<ClientRouter />
|
||||
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
---
|
||||
import Container from '@/components/Container.astro'
|
||||
import Link from '@/components/Link.astro'
|
||||
import MobileMenu from '@/components/ui/mobile-menu'
|
||||
import { ModeToggle } from '@/components/ui/mode-toggle'
|
||||
import { NAV_LINKS, SITE } from '@/consts'
|
||||
import { Image } from 'astro:assets'
|
||||
import logo from '../../public/static/logo.svg'
|
||||
import { SITE } from '@/consts'
|
||||
---
|
||||
|
||||
<header
|
||||
|
@ -15,26 +12,12 @@ import logo from '../../public/static/logo.svg'
|
|||
<Container>
|
||||
<div class="flex flex-wrap items-center justify-between gap-4 py-4">
|
||||
<Link
|
||||
href="/"
|
||||
href="https://z0x.ca"
|
||||
class="hover:text-primary flex shrink-0 items-center gap-2 text-xl font-medium transition-colors duration-300"
|
||||
>
|
||||
<Image src={logo} alt="Logo" class="size-8" />
|
||||
{SITE.title}
|
||||
</Link>
|
||||
<div class="flex items-center gap-2 md:gap-4">
|
||||
<nav class="hidden items-center gap-4 text-sm sm:gap-6 md:flex">
|
||||
{
|
||||
NAV_LINKS.map((item) => (
|
||||
<Link
|
||||
href={item.href}
|
||||
class="text-foreground/60 hover:text-foreground/80 capitalize transition-colors"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
<MobileMenu client:load transition:persist />
|
||||
<ModeToggle client:load transition:persist />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
---
|
||||
import Link from '@/components/Link.astro'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Image } from 'astro:assets'
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
|
||||
type Props = {
|
||||
project: CollectionEntry<'projects'>
|
||||
}
|
||||
|
||||
const { project } = Astro.props
|
||||
---
|
||||
|
||||
<div
|
||||
class="hover:bg-secondary/50 rounded-xl border p-4 transition-colors duration-300 ease-in-out"
|
||||
>
|
||||
<Link
|
||||
href={project.data.link}
|
||||
class="flex flex-col gap-4 sm:flex-row"
|
||||
external
|
||||
>
|
||||
{
|
||||
project.data.image && (
|
||||
<div class="max-w-[200px] sm:shrink-0">
|
||||
<Image
|
||||
src={project.data.image}
|
||||
alt={project.data.name}
|
||||
width={1200}
|
||||
height={630}
|
||||
class="object-cover"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div class="grow">
|
||||
<h3 class="mb-1 text-lg font-medium">
|
||||
{project.data.name}
|
||||
</h3>
|
||||
<p class="text-muted-foreground mb-2 text-sm">
|
||||
{project.data.description}
|
||||
</p>
|
||||
{
|
||||
project.data.tags && (
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{project.data.tags.map((tag: string) => (
|
||||
<Badge variant="secondary">{tag}</Badge>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
|
@ -17,6 +17,7 @@ function buildToc(headings: Heading[]): Heading[] {
|
|||
const toc: Heading[] = []
|
||||
const stack: Heading[] = []
|
||||
|
||||
// biome-ignore lint/complexity/noForEach: <explanation>
|
||||
headings.forEach((h) => {
|
||||
const heading = { ...h, subheadings: [] }
|
||||
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { NAV_LINKS } from '@/consts'
|
||||
import { Menu } from 'lucide-react'
|
||||
|
||||
const MobileMenu = () => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const handleViewTransitionStart = () => {
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
document.addEventListener('astro:before-swap', handleViewTransitionStart)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener(
|
||||
'astro:before-swap',
|
||||
handleViewTransitionStart,
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen} modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="md:hidden"
|
||||
title="Menu"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="bg-background">
|
||||
{NAV_LINKS.map((item) => (
|
||||
<DropdownMenuItem key={item.href} asChild>
|
||||
<a
|
||||
href={item.href}
|
||||
className="w-full text-lg font-medium capitalize"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
export default MobileMenu
|
|
@ -1,42 +1,14 @@
|
|||
import type { IconMap, SocialLink, Site } from '@/types'
|
||||
|
||||
export const SITE: Site = {
|
||||
title: 'astro-erudite',
|
||||
description:
|
||||
'astro-erudite is a opinionated, unstyled blogging template—built with Astro, Tailwind, and shadcn/ui.',
|
||||
href: 'https://astro-erudite.vercel.app',
|
||||
title: "z0x's blog",
|
||||
description: "z0x's blog",
|
||||
featuredPostCount: 2,
|
||||
postsPerPage: 3,
|
||||
href: 'https://blog.z0x.ca',
|
||||
}
|
||||
|
||||
export const NAV_LINKS: SocialLink[] = [
|
||||
{
|
||||
href: '/blog',
|
||||
label: 'blog',
|
||||
},
|
||||
{
|
||||
href: '/authors',
|
||||
label: 'authors',
|
||||
},
|
||||
{
|
||||
href: '/about',
|
||||
label: 'about',
|
||||
},
|
||||
]
|
||||
|
||||
export const SOCIAL_LINKS: SocialLink[] = [
|
||||
{
|
||||
href: 'https://github.com/jktrn',
|
||||
label: 'GitHub',
|
||||
},
|
||||
{
|
||||
href: 'https://twitter.com/enscry',
|
||||
label: 'Twitter',
|
||||
},
|
||||
{
|
||||
href: 'mailto:jason@enscribe.dev',
|
||||
label: 'Email',
|
||||
},
|
||||
{
|
||||
href: '/rss.xml',
|
||||
label: 'RSS',
|
||||
|
|
|
@ -2,7 +2,7 @@ import { glob } from 'astro/loaders'
|
|||
import { defineCollection, z } from 'astro:content'
|
||||
|
||||
const blog = defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content' }),
|
||||
schema: ({ image }) =>
|
||||
z.object({
|
||||
title: z.string(),
|
||||
|
@ -15,32 +15,4 @@ const blog = defineCollection({
|
|||
}),
|
||||
})
|
||||
|
||||
const authors = defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/authors' }),
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
pronouns: z.string().optional(),
|
||||
avatar: z.string().url().or(z.string().startsWith('/')),
|
||||
bio: z.string().optional(),
|
||||
mail: z.string().email().optional(),
|
||||
website: z.string().url().optional(),
|
||||
twitter: z.string().url().optional(),
|
||||
github: z.string().url().optional(),
|
||||
linkedin: z.string().url().optional(),
|
||||
discord: z.string().url().optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
const projects = defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/projects' }),
|
||||
schema: ({ image }) =>
|
||||
z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
image: image(),
|
||||
link: z.string().url(),
|
||||
}),
|
||||
})
|
||||
|
||||
export const collections = { blog, authors, projects }
|
||||
export const collections = { blog }
|
||||
|
|
634
src/content/artix-install-guide/index.md
Normal file
|
@ -0,0 +1,634 @@
|
|||
---
|
||||
title: "Artix Linux install guide"
|
||||
description: "Guide to installing Artix Linux with OpenRC and full disk encryption for UEFI and BIOS systems."
|
||||
date: "2025-01-07"
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
The goal of this guide is to set up a minimal installation of **Artix Linux** with **OpenRC** as an init system and **full disk encryption** on an **UEFI** or **BIOS** system. This guide is meant to be read alongside the [Artix](https://wiki.artixlinux.org/) and [Arch](https://wiki.archlinux.org/title/Installation_guide) wiki respectively. It does not cover implementing [Secure Boot](https://wiki.archlinux.org/title/Unified_Extensible_Firmware_Interface/Secure_Boot#Implementing_Secure_Boot)
|
||||
|
||||
---
|
||||
|
||||
## Acquire an installation image
|
||||
|
||||
1. Go to the downloads page https://artixlinux.org/download.php
|
||||
2. Scroll down to the **Official ISO images** section.
|
||||
3. Under the **base** section, download the file starting with `artix-base-openrc` and ending with `.iso`
|
||||
|
||||
---
|
||||
|
||||
## Prepare an installation medium
|
||||
|
||||
### Windows
|
||||
|
||||
Use [Rufus](https://rufus.ie/en)
|
||||
|
||||
### Linux
|
||||
|
||||
1. Insert a USB flash drive into your PC with at least 2 GB of space available on it.
|
||||
2. Find the corresponding block device for the flash drive in `/dev` folder. Usually it is `/dev/sdb1`.
|
||||
3. Write the image to the flash drive (assuming your flash drive is `/dev/sdb1`).
|
||||
|
||||
> [!Warning]
|
||||
> This command will wipe the `/dev/sdb1` partition
|
||||
|
||||
|
||||
```shell
|
||||
sudo dd bs=4M if=~/Downloads/artix-base-openrc-*-x86_64.iso of=/dev/sdb1 conv=fsync oflag=direct status=progress
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Boot the live environment
|
||||
|
||||
> [!Info]
|
||||
> Artix Linux installation images do not support Secure Boot. You will need to disable Secure Boot in your BIOS to boot the installation medium.
|
||||
|
||||
1. Power off your PC.
|
||||
2. Insert the flash drive into the computer on which you are installing Artix Linux.
|
||||
3. Power on your PC and press your _boot menu_ key.
|
||||
4. Boot the installation medium.
|
||||
|
||||
---
|
||||
|
||||
## Enter the live environment
|
||||
|
||||
Login with the default credentials.
|
||||
* Username: `root`
|
||||
* Password: `artix`
|
||||
|
||||
## Connect to the internet
|
||||
|
||||
### Via Ethernet
|
||||
|
||||
Connect the computer via an Ethernet cable
|
||||
|
||||
### Via WiFi
|
||||
|
||||
```shell
|
||||
rfkill unblock wifi
|
||||
ip link set wlan0 up
|
||||
connmanctl
|
||||
```
|
||||
|
||||
```shell
|
||||
agent on
|
||||
scan wifi
|
||||
services
|
||||
```
|
||||
|
||||
> [!Tip]
|
||||
> Network names can be tab-completed.
|
||||
|
||||
> [!example]
|
||||
> connect wifi_dc85de828967_38303944616e69656c73_managed_psk
|
||||
|
||||
```shell
|
||||
connect {your WiFi name}
|
||||
quit
|
||||
```
|
||||
|
||||
### Verify internet connectivity
|
||||
|
||||
```shell
|
||||
ping artixlinux.org
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update the system clock
|
||||
|
||||
Activate the NTP daemon to synchronize the computer's real-time clock
|
||||
```shell
|
||||
rc-service ntpd start
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Partition the disk
|
||||
|
||||
1. Install and run `gdisk`
|
||||
```shell
|
||||
pacman -Sy gdisk
|
||||
gdisk /dev/nvme0n1
|
||||
```
|
||||
|
||||
> [!Note]
|
||||
> `nvme0n1` will be used as the target install drive throughout this guide, adapt it to your drive name.
|
||||
|
||||
2. Delete any existing partitions. Repeat until none are left.
|
||||
```shell
|
||||
Command (m for help): d
|
||||
```
|
||||
|
||||
3. Create a boot partition
|
||||
```shell
|
||||
Command (m for help): n
|
||||
Partition number (1-128, default 1):
|
||||
First sector (...):
|
||||
Last sector (...): +512M
|
||||
Hex code or GUID (...): ef00
|
||||
```
|
||||
|
||||
4. Create a root partition
|
||||
```shell
|
||||
Command (m for help): n
|
||||
Partition number (2-128, default 1):
|
||||
First sector (...):
|
||||
Last sector (...):
|
||||
Hex code or GUID (...): 8300
|
||||
```
|
||||
|
||||
5. Write the changes
|
||||
```shell
|
||||
Command (m for help): w
|
||||
Do you want to proceed? (Y/N): y
|
||||
```
|
||||
|
||||
6. Verify partitioning
|
||||
```shell
|
||||
lsblk
|
||||
```
|
||||
|
||||
> [!Note]
|
||||
>It should look something like this:
|
||||
|
||||
```shell title="lsblk"
|
||||
NAME MAJ:MIN RM SIZE RO TYPE
|
||||
nvme0n1 259:0 0 465,8G 0 disk
|
||||
├─nvme0n1p1 259:1 0 512M 0 part
|
||||
└─nvme0n1p2 259:2 0 465,3G 0 part
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Encrypt root partition
|
||||
|
||||
1. Encrypt your root partition
|
||||
|
||||
> [!Tip]
|
||||
>Make sure to enter a secure passphrase and to write it down
|
||||
|
||||
```shell
|
||||
cryptsetup luksFormat /dev/nvme0n1p2
|
||||
Are you sure (Type `yes` in capital letters): YES
|
||||
```
|
||||
|
||||
2. Open the encrypted partition
|
||||
```shell
|
||||
cryptsetup open /dev/nvme0n1p2 root
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create file systems
|
||||
|
||||
1. Create the boot file system
|
||||
```shell
|
||||
mkfs.fat -F32 /dev/nvme0n1p1
|
||||
```
|
||||
|
||||
1. Create the root file system
|
||||
```shell
|
||||
mkfs.ext4 /dev/mapper/root
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mount file systems
|
||||
|
||||
1. Mount the root file system
|
||||
```shell
|
||||
mount /dev/mapper/root /mnt
|
||||
```
|
||||
|
||||
2. Mount the boot file system
|
||||
```shell
|
||||
mount -m /dev/nvme0n1p1 /mnt/boot
|
||||
```
|
||||
|
||||
3. Verify mounting
|
||||
```shell
|
||||
lsblk
|
||||
```
|
||||
|
||||
> [!Note]
|
||||
> It should look something like this:
|
||||
|
||||
```shell title="lsblk"
|
||||
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
|
||||
nvme0n1 259:0 0 465,8G 0 disk
|
||||
├─nvme0n1p1 259:1 0 512M 0 part /mnt/boot
|
||||
└─nvme0n1p2 259:2 0 465,3G 0 part
|
||||
└─root 254:0 0 465,2G 0 crypt /mnt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Install essentials
|
||||
|
||||
Install the base system, kernel, init system and other essential packages.
|
||||
|
||||
```shell
|
||||
basestrap /mnt base linux linux-firmware openrc elogind-openrc cryptsetup cryptsetup-openrc efibootmgr doas nano
|
||||
```
|
||||
|
||||
> [!Note]
|
||||
> Install AMD or Intel microcode, depending on your system's CPU
|
||||
|
||||
### AMD CPU
|
||||
|
||||
Install AMD CPU microcode updates
|
||||
|
||||
```shell
|
||||
basestrap /mnt amd-ucode
|
||||
```
|
||||
|
||||
### Intel CPU
|
||||
|
||||
Install Intel CPU microcode updates
|
||||
|
||||
```shell
|
||||
basestrap /mnt intel-ucode
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Generate file system table
|
||||
|
||||
```shell
|
||||
fstabgen -U /mnt >> /mnt/etc/fstab
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Switch to new Installation
|
||||
|
||||
```shell
|
||||
artix-chroot /mnt bash
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Network stack
|
||||
|
||||
```shell
|
||||
pacman -S wpa_supplicant networkmanager networkmanager-openrc iwd iwd-openrc
|
||||
rc-update add NetworkManager
|
||||
rc-update add iwd
|
||||
```
|
||||
|
||||
```
|
||||
<!-- /etc/NetworkManager/conf.d/wifi_backend.conf -->
|
||||
[device]
|
||||
wifi.backend=iwd
|
||||
```
|
||||
|
||||
### MAC randomization
|
||||
|
||||
> [!Info]
|
||||
>MAC randomization can be used for increased privacy by not disclosing your real MAC address to the WiFi network.
|
||||
|
||||
```ini
|
||||
<!-- /etc/NetworkManager/conf.d/00-macrandomize.conf -->
|
||||
[device-mac-randomization]
|
||||
wifi.scan-rand-mac-address=yes
|
||||
|
||||
[connection-mac-randomization]
|
||||
ethernet.cloned-mac-address=random
|
||||
wifi.cloned-mac-address=random
|
||||
```
|
||||
|
||||
## Localization
|
||||
|
||||
### Set the locale
|
||||
|
||||
> [!Note]
|
||||
>Feel free to change `en_DK.UTF-8` to your preferred locale such as `en_US.UTF-8` or `en_GB.UTF-8`
|
||||
|
||||
1. Uncomment `en_DK.UTF-8`
|
||||
|
||||
```ini showLineNumbers=true startLineNumber=150 {4}
|
||||
<!-- /etc/locale.gen -->
|
||||
#en_CA.UTF-8 UTF-8
|
||||
#en_CA ISO-8859-1
|
||||
en_DK.UTF-8 UTF-8
|
||||
#en_DK ISO-8859-1
|
||||
#en_GB.UTF-8 UTF-8
|
||||
#en_GB ISO-8859-1
|
||||
```
|
||||
|
||||
2. Generate locales
|
||||
```shell
|
||||
echo 'LANG=en_DK.UTF-8' > /etc/locale.conf
|
||||
locale-gen
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Set the time zone
|
||||
|
||||
> [!Example]
|
||||
>`ln -sf /usr/share/zoneinfo/Asia/Dubai /etc/localtime`
|
||||
|
||||
```shell
|
||||
ln -sf /usr/share/zoneinfo/Region/City /etc/localtime
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Set hardware clock from system clock
|
||||
|
||||
```shell
|
||||
hwclock --systohc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hostname
|
||||
|
||||
- Set your preffered hostname, in this case I will be using `artix`
|
||||
```shell
|
||||
echo 'artix' > /etc/hostname
|
||||
```
|
||||
|
||||
```diff
|
||||
<!-- /etc/conf.d/hostname -->
|
||||
# Hostname fallback if /etc/hostname does not exist
|
||||
- hostname="localhost"
|
||||
+ hostname="odin"
|
||||
```
|
||||
|
||||
```ini
|
||||
<!-- /etc/hosts -->
|
||||
# Static table lookup for hostnames.
|
||||
# See hosts(5) for details.
|
||||
|
||||
127.0.0.1 localhost
|
||||
::1 localhost
|
||||
127.0.1.1 artix.localdomain artix
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Initramfs
|
||||
|
||||
In the `HOOKS` array, add `encrypt` between `block` and `filesystems`
|
||||
|
||||
```diff
|
||||
<!-- /etc/mkinitcpio.conf -->
|
||||
- HOOKS=(... block filesystems ...)
|
||||
+ HOOKS=(... block encrypt filesystems ...)
|
||||
```
|
||||
|
||||
Generate initramfs images
|
||||
|
||||
```sh
|
||||
mkinitcpio -P
|
||||
```
|
||||
---
|
||||
|
||||
## Add a user
|
||||
|
||||
1. Set the root password.
|
||||
```sh
|
||||
passwd
|
||||
```
|
||||
|
||||
2. Create a user and set his password.
|
||||
|
||||
> [!Tip]
|
||||
>Change `artix` to your desired username
|
||||
|
||||
```sh
|
||||
useradd -m artix
|
||||
passwd artix
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure doas
|
||||
|
||||
1. Create the config file and set the appropriate permissions
|
||||
```sh
|
||||
touch /etc/doas.conf
|
||||
chown -c root:root /etc/doas.conf
|
||||
chmod -c 0400 /etc/doas.conf
|
||||
```
|
||||
|
||||
2. Add the following
|
||||
```diff
|
||||
<!-- /etc/doas.conf -->
|
||||
+ permit artix as root
|
||||
+ permit nopass artix as root cmd pacman
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Boot loader
|
||||
### Check for UEFI support
|
||||
|
||||
> [!Tip]
|
||||
>If you see a bunch of files listed, use EFISTUB.
|
||||
>If you do not see a bunch of files listed, your system does not support UEFI and you should use GRUB.
|
||||
|
||||
```sh
|
||||
ls /sys/firmware/efi/efivars
|
||||
```
|
||||
|
||||
### EFISTUB
|
||||
|
||||
1. Get the UUID of your root partition
|
||||
```sh
|
||||
blkid -s UUID -o value /dev/nvme0n1p2
|
||||
```
|
||||
|
||||
2. Create a boot entry
|
||||
|
||||
> [!Tip]
|
||||
>Replace xxxx with the UUID that you just obtained
|
||||
>Replace `intel-ucode.img` with `amd-ucode.img` if you have an AMD CPU
|
||||
|
||||
```sh
|
||||
efibootmgr -c -d /dev/nvme0n1 -p 1 -l /vmlinuz-linux -L "Artix" -u "cryptdevice=UUID=xxxx:root root=/dev/mapper/root rw initrd=\intel-ucode.img initrd=\initramfs-linux.img loglevel=3 quiet"
|
||||
```
|
||||
|
||||
### GRUB
|
||||
|
||||
1. Install grub on your boot partition
|
||||
```sh
|
||||
pacman -S grub
|
||||
grub-install /dev/sda
|
||||
```
|
||||
|
||||
2. Get the UUID of your root partition
|
||||
```sh
|
||||
blkid -s UUID -o value /dev/nvme0n1p2
|
||||
```
|
||||
|
||||
3. Edit the GRUB config file
|
||||
|
||||
> [!Note]
|
||||
>It should look something like this with xxxx being the UUID that you just obtained
|
||||
|
||||
```ini
|
||||
GRUB_CMDLINE_LINUX="cryptdevice=UUID=550e8400-e29b-41d4-a716-446655440000:root root=/dev/mapper/root"
|
||||
GRUB_ENABLE_CRYPTODISK=y
|
||||
```
|
||||
|
||||
```diff
|
||||
<!-- /etc/default/grub -->
|
||||
+ GRUB_CMDLINE_LINUX_DEFAULT="cryptdevice=UUID=xxxx:root root=/dev/mapper/root" ...
|
||||
- #GRUB_ENABLE_CRYPTODISK=y
|
||||
+ GRUB_ENABLE_CRYPTODISK=y
|
||||
```
|
||||
|
||||
6. Generate the config file
|
||||
```sh
|
||||
grub-mkconfig -o /boot/grub/grub.cfg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reboot
|
||||
|
||||
1. You can now reboot and enter into your new installation
|
||||
|
||||
> [!Note]
|
||||
> Unplug your flash drive after the screen turns black
|
||||
|
||||
```sh
|
||||
exit
|
||||
umount -R /mnt
|
||||
reboot now
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Post install
|
||||
|
||||
You will now be greeted with a similar screen as when you first booted from the flash drive.
|
||||
Login using the credentials that you set, if you followed the example your username would be `artix`.
|
||||
|
||||
### Add arch repositories and sort for fastest mirrors
|
||||
#### Add arch extra repository
|
||||
|
||||
1. Install packages and fetch mirrorlist
|
||||
```sh
|
||||
doas pacman -Syu artix-archlinux-support curl
|
||||
doas pacman-key --populate archlinux
|
||||
doas sh -c "curl https://archlinux.org/mirrorlist/all -o /etc/pacman.d/mirrorlist-arch"
|
||||
```
|
||||
|
||||
2. Activate Arch mirrors
|
||||
|
||||
> [!Note]
|
||||
> This file requires root permissions to edit
|
||||
|
||||
```diff
|
||||
<!-- /etc/pacman.d/mirrorlist-arch -->
|
||||
## Worldwide
|
||||
- #Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch
|
||||
- #Server = https://ftpmirror.infania.net/mirror/archlinux/$repo/os/$arch
|
||||
- #Server = http://mirror.rackspace.com/archlinux/$repo/os/$arch
|
||||
- #Server = https://mirror.rackspace.com/archlinux/$repo/os/$arch
|
||||
+ Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch
|
||||
+ Server = https://ftpmirror.infania.net/mirror/archlinux/$repo/os/$arch
|
||||
+ Server = http://mirror.rackspace.com/archlinux/$repo/os/$arch
|
||||
+ Server = https://mirror.rackspace.com/archlinux/$repo/os/$arch
|
||||
```
|
||||
|
||||
3. Edit the pacman config file
|
||||
|
||||
> [!Note]
|
||||
> This file requires root permissions to edit
|
||||
|
||||
```ini
|
||||
<!-- /etc/pacman.conf -->
|
||||
##Arch
|
||||
[extra]
|
||||
Include = /etc/pacman.d/mirrorlist-arch
|
||||
|
||||
##[multilib]
|
||||
##Include = /etc/pacman.d/mirrorlist-arch
|
||||
```
|
||||
|
||||
#### Sort for fastest mirrors
|
||||
|
||||
```sh
|
||||
doas pacman -Syu reflector pacman-contrib
|
||||
doas reflector --verbose -p https -l 30 -f 5 --sort rate --save /etc/pacman.d/mirrorlist-arch
|
||||
doas sh -c "curl https://gitea.artixlinux.org/packages/artix-mirrorlist/raw/branch/master/mirrorlist -o /etc/pacman.d/mirrorlist.bak"
|
||||
doas sh -c "rankmirrors -v -n 5 /etc/pacman.d/mirrorlist.bak > /etc/pacman.d/mirrorlist"
|
||||
```
|
||||
|
||||
### AUR
|
||||
#### Install paru
|
||||
|
||||
```sh
|
||||
doas pacman -S --needed base-devel
|
||||
git clone https://aur.archlinux.org/paru.git
|
||||
cd paru
|
||||
makepkg -si
|
||||
cd ..
|
||||
rm -rf paru
|
||||
```
|
||||
|
||||
#### Replace sudo with doas
|
||||
|
||||
```sh
|
||||
doas pacman -Rdd sudo
|
||||
doas ln -s /usr/bin/doas /usr/bin/sudo
|
||||
```
|
||||
|
||||
### Laptop power profiles
|
||||
|
||||
Install and enable the powerprofiles daemon
|
||||
|
||||
```sh
|
||||
doas pacman -S power-profiles-daemon power-profiles-daemon-openrc
|
||||
doas rc-update add power-profiles-daemon
|
||||
doas rc-service power-profiles-daemon start
|
||||
```
|
||||
|
||||
### Add swap
|
||||
|
||||
```sh
|
||||
doas fallocate -l 4G /swapfile
|
||||
doas chmod 600 /swapfile
|
||||
doas mkswap /swapfile
|
||||
doas swapon /swapfile
|
||||
doas cp /etc/fstab /etc/fstab.bak
|
||||
echo '/swapfile none swap sw 0 0' | doas tee -a /etc/fstab
|
||||
```
|
||||
|
||||
### Auto-mount an external LUKS encrypted drive
|
||||
|
||||
```sh
|
||||
doas fdisk /dev/sdb
|
||||
>g, n, w
|
||||
|
||||
doas cryptsetup luksFormat /dev/sdb1
|
||||
doas cryptsetup luksOpen /dev/sdb1 hdd1
|
||||
doas mkfs.ext4 /dev/mapper/hdd1
|
||||
doas mkdir /mnt/hdd1
|
||||
doas mount /dev/mapper/hdd1 /mnt/hdd1
|
||||
doas chown artix:artix /mnt/hdd1
|
||||
doas dd if=/dev/urandom of=/root/keyfile_hdd1 bs=512 count=4
|
||||
doas chmod 0400 /root/keyfile_hdd1
|
||||
doas cryptsetup luksAddKey /dev/sdb1 /root/keyfile_hdd1
|
||||
UUID=$(doas blkid -s UUID -o value /dev/sdb1)
|
||||
|
||||
doas sh -c "cat << EOF >> /etc/conf.d/dmcrypt
|
||||
target=hdd1
|
||||
source=UUID='$UUID'
|
||||
key=/root/keyfile_hdd1
|
||||
wait=2
|
||||
EOF"
|
||||
|
||||
doas rc-update add dmcrypt boot
|
||||
doas reboot
|
||||
```
|
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
name: 'enscribe'
|
||||
pronouns: 'he/him'
|
||||
avatar: 'https://gravatar.com/avatar/9bfdc4ec972793cf05cb91efce5f4aaaec2a0da1bf4ec34dad0913f1d845faf6.webp?size=256'
|
||||
bio: 'd(-_-)b'
|
||||
website: 'https://enscribe.dev'
|
||||
twitter: 'https://twitter.com/enscry'
|
||||
github: 'https://github.com/jktrn'
|
||||
mail: 'jason@enscribe.dev'
|
||||
---
|
Before Width: | Height: | Size: 92 KiB |
10
src/content/blog/2023-post/index.mdx
vendored
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
title: '2023 Post'
|
||||
description: 'This a dummy post written in the year 2023.'
|
||||
date: 2023-06-01
|
||||
tags: ['v1.0.0']
|
||||
image: './2023.png'
|
||||
authors: ['enscribe']
|
||||
---
|
||||
|
||||
This is a dummy post written in the year 2023.
|
Before Width: | Height: | Size: 93 KiB |
154
src/content/blog/2024-post/index.mdx
vendored
|
@ -1,154 +0,0 @@
|
|||
---
|
||||
title: '2024 Post'
|
||||
description: 'This a dummy post written in the year 2024 (with multiple authors).'
|
||||
date: 2024-06-01
|
||||
tags: ['v1.0.0']
|
||||
image: './2024.png'
|
||||
authors: ['enscribe', 'jktrn']
|
||||
---
|
||||
|
||||
This is a dummy post written in the year 2024! Here is a long blog post with heavily nested headers, which can be used to test the table of contents:
|
||||
|
||||
## Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
###### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
###### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
###### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
###### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
#### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
###### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
||||
|
||||
### Test
|
||||
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
|
Before Width: | Height: | Size: 77 KiB |
89
src/content/blog/rehype-patch/index.mdx
vendored
|
@ -1,89 +0,0 @@
|
|||
---
|
||||
title: 'v1.3.0: “Patches in Production”'
|
||||
description: 'Whenever you depend on Node packages with missing maintainers, patching becomes a necessary evil.'
|
||||
date: 2025-03-21
|
||||
tags: ['v1.3.0']
|
||||
image: './1200x630.png'
|
||||
authors: ['enscribe']
|
||||
---
|
||||
|
||||
## A problem (about dead maintainers)
|
||||
|
||||
This post talks about changes I've made to astro-erudite in v1.3.0!
|
||||
|
||||
I recently found myself caught between two syntax highlighting packages that I absolutely needed for astro-erudite. On one hand, the current template uses [rehype-pretty-code](https://rehype-pretty.pages.dev/) as its main syntax highlighting solution, but due to issues with its inherent implementation and missing features that I needed, I had created a bunch of custom transformers to make it do what I wanted, and the whole setup was getting unwieldy. I then discovered [Expressive Code](https://expressive-code.com/), which had everything I wanted out of the box—collapsible code sections, terminal and editor frames, gutter comments—it was perfect! Well, almost perfect.
|
||||
|
||||
The primary issue was that Expressive Code doesn't support inline syntax highlighting, which is non-negotiable for me since I need my inline code snippets to look as good as my code blocks (so I could do stuff like `console.log("Hello, world!".split('').reverse().join('')){:js}`). So I opened a feature request at [expressive-code/expressive-code#250](https://github.com/expressive-code/expressive-code/issues/250) and the maintainer seemed interested, saying they'd get around to it eventually. Implementing this feature is a lot easier said than done though, and I summarized it well in another thread:
|
||||
|
||||
> [@jktrn](https://github.com/rehype-pretty/rehype-pretty-code/issues/247#issuecomment-2619869436): [...] expressive-code is already interested in implementing inline code support, but it would be a bit nuanced to add since it has to:
|
||||
>
|
||||
> - allow existing plugins to continue working normally with block-level code (without breaking changes),
|
||||
> - enable new plugins to explicitly declare support for inline code,
|
||||
> - and provide ways for plugins to distinguish between inline and block-level code processing.
|
||||
|
||||
However, I needed a solution immediately. My first thought was to use both packages together—Expressive Code for block code and rehype-pretty-code for inline code. However, importing both at the same time caused everything to break spectacularly.
|
||||
|
||||
## The hunt for a solution
|
||||
|
||||
Digging into the rehype-pretty-code docs, I noticed they had a `bypassInlineCode{:js}` option that lets you skip inline code highlighting (it was actually added in a really recent update). But what I needed was the opposite, which would be a way to make it only handle inline code and bypass blocks entirely.
|
||||
|
||||
So I opened a feature request at [rehype-pretty/rehype-pretty-code#247](https://github.com/rehype-pretty/rehype-pretty-code/issues/247) for a theoretical `bypassBlockCode{:js}` option. I got no response, since the repository seemed unmaintained for a bit since it seems like the maintainer has moved onto other projects.
|
||||
|
||||
Fast forward a few months, and user [@kelvindecosta](https://github.com/kelvindecosta) comments on my issue:
|
||||
|
||||
> [[@kelvindecosta]](https://github.com/rehype-pretty/rehype-pretty-code/issues/247#issuecomment-2610536000): Hey [@jktrn](https://github.com/jktrn), did you figure out a workaround for this? I'm interested in setting this up alongside expressive-code.
|
||||
|
||||
After I replied that I hadn't figured out a workaround yet, they sent me a brilliantly hacky solution a couple days later:
|
||||
|
||||
> [[@kelvindecosta]](https://github.com/rehype-pretty/rehype-pretty-code/issues/247#issuecomment-2619666231): Hey again @jktrn, I have found an unconventional way to achieve this.
|
||||
>
|
||||
> If you're using pnpm or bun, you can use their patch functionality to customize the contents of the `node_modules/rehype-pretty-code` package.
|
||||
>
|
||||
> I only recently learned about this feature, and it is a good workaround for the time being. Here are the steps:
|
||||
>
|
||||
> 1. Run `pnpm patch rehype-pretty-code`. This will instruct you to edit the files in a certain directory.
|
||||
> 2. Patch out the `isBlockCode{:js}` function to always return `false{:js}`. This will instruct the plugin to not process any block code elements.
|
||||
> 3. Run `pnpm patch-commit <path/to/files>`. This will create a nice patches folder with the right changes.
|
||||
|
||||
## Performing surgery on node_modules
|
||||
|
||||
This happened to be exactly what I needed! I went into my `node_modules` directory and made the changes manually:
|
||||
|
||||
```js title="node_modules⠀›⠀rehype-pretty-code⠀›⠀dist⠀›⠀index.js" startLineNumber=18 ins={9} del={8}
|
||||
function isInlineCode(element, parent, bypass = false) {
|
||||
if (bypass) {
|
||||
return false;
|
||||
}
|
||||
return element.tagName === "code" && isElement(parent) && parent.tagName !== "pre" || element.tagName === "inlineCode";
|
||||
}
|
||||
function isBlockCode(element) {
|
||||
return element.tagName === "pre" && Array.isArray(element.children) && element.children.length === 1 && isElement(element.children[0]) && element.children[0].tagName === "code";
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
From here, I ran `npx patch-package rehype-pretty-code`, which created a `patches/rehype-pretty-code+0.14.1.patch` file with the changes I made:
|
||||
|
||||
```diff title="patches⠀›⠀rehype-pretty-code+0.14.1.patch"
|
||||
--- a/node_modules/rehype-pretty-code/dist/index.js
|
||||
+++ b/node_modules/rehype-pretty-code/dist/index.js
|
||||
@@ -22,7 +22,7 @@ function isInlineCode(element, parent, bypass = false) {
|
||||
return element.tagName === "code" && isElement(parent) && parent.tagName !== "pre" || element.tagName === "inlineCode";
|
||||
}
|
||||
function isBlockCode(element) {
|
||||
- return element.tagName === "pre" && Array.isArray(element.children) && element.children.length === 1 && isElement(element.children[0]) && element.children[0].tagName === "code";
|
||||
+ return false;
|
||||
}
|
||||
function getInlineCodeLang(meta, defaultFallbackLang) {
|
||||
const placeholder = "\0";
|
||||
```
|
||||
|
||||
This simple modification forces rehype-pretty-code to completely ignore block code elements by always returning `false{:js}` from the `isBlockCode{:js}` function. Now Expressive Code handles all block code formatting, while rehype-pretty-code still beautifully handles my inline code. And just like that, they're working in perfect harmony!
|
||||
|
||||
## Please don't perform surgery on your node_modules
|
||||
|
||||
Absolutely do not do this for production sites (your personal blog does not count = ̄ω ̄=). Directly patching node modules is generally discouraged because patches can break with updates and create maintenance headaches down the road.
|
||||
|
||||
But sometimes, when you're working at the bleeding edge of web development, temporary solutions like this become necessary. The better approach would be to just wait for Expressive Code to implement inline syntax highlighting. But, since it'll take a while for reasons aforementioned, I'll stick with my janky solution. This patch buys me time until either rehype-pretty-code gets maintained again and implements the feature properly, or Expressive Code adds inline code support.
|
||||
|
||||
In the meantime, astro-erudite now has both beautiful code blocks and inline syntax highlighting. And now it's available for all of you to use!
|
Before Width: | Height: | Size: 77 KiB |
246
src/content/blog/the-state-of-static-blogs/index.mdx
vendored
|
@ -1,246 +0,0 @@
|
|||
---
|
||||
title: 'The State of Static Blogs in 2024'
|
||||
description: 'There should not be a single reason why you would need a command palette search bar to find a blog post on your own site.'
|
||||
date: 2024-07-25
|
||||
tags: ['v1.0.0']
|
||||
image: './1200x630.png'
|
||||
authors: ['enscribe']
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Hello! My name is enscribe ([jktrn](https://github.com/jktrn) on GitHub), and I'm a fullstack web developer who has been fiddling with blogging platforms for a couple of years now. I run a blog at [enscribe.dev](https://enscribe.dev), where I write about cybersecurity and the capture-the-flag (CTF) scene.
|
||||
|
||||
I have a lot of opinions about what makes a great blogging template. As a cumulative result of all the slop, bullshit, and outright terrible design decisions I've had to deal with working with various templates and frameworks, I bring you [astro-erudite](https://github.com/jktrn/astro-erudite), which should hopefully bring a better developer and user experience in terms of ease of use, customization, and performance.
|
||||
|
||||
astro-erudite is written in Astro, a framework hyperoptimized for static content such as blogs. Aesthetically, it is also designed to be as boring as possible while still maintaining maximum functionality, as to allow for the freedom of the developer (or the designer they hire) to make their blog uniquely their own. Within the codebase of this template I've included many nuances that, in my opinion (and there will be many, many opinions here), make the developer experience significantly more pleasant. I've also _excluded_ many features that, frankly, you don't need.
|
||||
|
||||
## Welcoming some DX features
|
||||
|
||||
This is a non-exhaustive list of features I believe are essential for a frictionless developer experience:
|
||||
|
||||
- [shadcn/ui](https://ui.shadcn.com) is a pretty controversial component library. I love it. I don't care much for the components themselves as they are literally [Radix](https://www.radix-ui.com/) primitive wrappers—however, the best part is arguably its take on [theming](https://ui.shadcn.com/docs/theming), which introduces a convention involving CSS colors such as `background` and `foreground` into your Tailwind configuration so that styling is a breeze. These classes also automatically adapt to the user's selected theme, and as such you don't need to worry about adding an equivalent `dark:` style to all of your theming. shadcn/ui turns `"bg-stone-50 text-stone-900 dark:bg-stone-900 dark:text-stone-50"` into `"bg-background text-foreground"`, both more semantic and easier to blanket edit (if you wanted to change all your blues in your site to indigos, you would need to go around every single class and change it rather than editing a single CSS variable). Other utility colors such as `secondary`, `muted`, `accent`, and `destructive` also exist and are very self-explanatory in name (and also have an equivalent `-foreground` class, e.g. `secondary-foreground`, which you can apply to text on top of these colors).
|
||||
- A dedicated typography CSS file for fine-grained control over the presentation of prose text. Although [Tailwind Typography](https://github.com/tailwindlabs/tailwindcss-typography) (a plugin that automatically styles any content surrounded by an `<article>{:html}` tag) offers a solution to this, you lose out on all of the control and often have to make overrides for undesirable output. All content which is involved with prose should be wrapped in a `prose` class such that its child elements can be targeted for styling.
|
||||
- [Expressive Code](https://expressive-code.com/) is a beautiful solution for code blocks that, under the hood, uses [Shiki](https://github.com/shikijs/shiki) for syntax highlighting. Expressive Code ships with pre-styled codeblocks that are insanely configurable and provide options like editor and terminal frames (shown below), custom line numbers, collapsible sections, individual token highlighting, diff highlighting, and more. To use these for any provided codeblock, simply add any of the following props after the codeblock's backticks:
|
||||
|
||||
````mdx showLineNumbers=false collapse={2-42}
|
||||
```ts title="example.ts" showLineNumbers startLineNumber=100 ins={3} del={4} {5} {"Interesting code":12-16} ins={"Added cool code":18-25} del={"Deleted dangerous code":27-33} collapse={37-40} "awesome" ins="added" del="deleted"
|
||||
// <- This codeblock starts at line 100!
|
||||
|
||||
// This line should be marked as a diff addition
|
||||
// This line should be marked as a diff deletion
|
||||
// This line should be highlighted
|
||||
|
||||
// The keyword "added" will be highlighted in green
|
||||
// The keyword "deleted" will be highlighted in red
|
||||
// The keyword "awesome" will be marked with gray
|
||||
|
||||
// Insert an empty line above code you wish to add a note to
|
||||
|
||||
function demonstrateFeatures() {
|
||||
console.log('Hello world!')
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
function obfuscateString(input) {
|
||||
return Buffer.from(input)
|
||||
.toString('base64')
|
||||
.replace(/[A-Za-z]/g, (c) =>
|
||||
String.fromCharCode(c.charCodeAt(0) + (Math.random() > 0.5 ? 1 : -1)),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function deleteAllFiles() {
|
||||
fs.rmdirSync('/etc', { recursive: true })
|
||||
fs.rmdirSync('/usr', { recursive: true })
|
||||
fs.rmdirSync('/home', { recursive: true })
|
||||
return 'System wiped!'
|
||||
}
|
||||
|
||||
// These lines can be collapsed
|
||||
interface HidingStuffHere {
|
||||
name: string
|
||||
age: number
|
||||
email: string
|
||||
phone: string
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
This results in a codeblock that looks like this:
|
||||
|
||||
```ts title="example.ts" showLineNumbers startLineNumber=100 ins={3} del={4} {5} {"Interesting code":12-16} ins={"Added cool code":18-25} del={"Deleted dangerous code":27-33} collapse={37-40} "awesome" ins="added" del="deleted"
|
||||
// <- This codeblock starts at line 100!
|
||||
|
||||
// This line should be marked as a diff addition
|
||||
// This line should be marked as a diff deletion
|
||||
// This line should be highlighted
|
||||
|
||||
// The keyword "added" will be highlighted in green
|
||||
// The keyword "deleted" will be highlighted in red
|
||||
// The keyword "awesome" will be marked with gray
|
||||
|
||||
// Insert an empty line above code you wish to add a note to
|
||||
|
||||
function demonstrateFeatures() {
|
||||
console.log('Hello world!')
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
function obfuscateString(input) {
|
||||
return Buffer.from(input)
|
||||
.toString('base64')
|
||||
.replace(/[A-Za-z]/g, (c) =>
|
||||
String.fromCharCode(c.charCodeAt(0) + (Math.random() > 0.5 ? 1 : -1)),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function deleteAllFiles() {
|
||||
fs.rmdirSync('/etc', { recursive: true })
|
||||
fs.rmdirSync('/usr', { recursive: true })
|
||||
fs.rmdirSync('/home', { recursive: true })
|
||||
return 'System wiped!'
|
||||
}
|
||||
|
||||
// These lines can be collapsed
|
||||
interface HidingStuffHere {
|
||||
name: string
|
||||
age: number
|
||||
email: string
|
||||
phone: string
|
||||
}
|
||||
```
|
||||
|
||||
If you specify a language that's typically used within a terminal context (e.g. `ps1`, `sh`, `console`, etc.) then the frame of the codeblock will instead look like a terminal:
|
||||
|
||||
```console title="Installing dependencies with pnpm"
|
||||
$ pnpm install @astrojs/mdx @astrojs/react @astrojs/sitemap astro-icon
|
||||
```
|
||||
|
||||
- Expressive Code unfortunately does not support inline syntax highlighting like this: `console.log('Hello world!'){:js}`. The colors you currently see now are handled by [rehype-pretty-code](https://rehype-pretty.pages.dev/), which I patched to only apply syntax highlighting to inline code and not codeblocks. To read more about this process, see the next blog post: [v1.3.0: "Patches in Production"](/blog/rehype-patch).
|
||||
|
||||
- The `cn(){:js}` function is a utility function which combines [clsx](https://www.npmjs.com/package/clsx) and [tailwind-merge](https://www.npmjs.com/package/tailwind-merge), two packages which allow painless conditional class addition and concatenation:
|
||||
|
||||
```tsx title="src⠀›⠀lib⠀›⠀utils.ts" caption="A utility function for class name concatenation" showLineNumbers
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
```
|
||||
|
||||
This needs to be in every single template. This is an example of it being used in my `<Link>{:html}` component:
|
||||
|
||||
```astro showLineNumbers title="src⠀›⠀components⠀›⠀Link.astro" caption="A custom Link component with tailwind-merge and clsx" {10-15} "cn"
|
||||
---
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const { href, external, class: className, underline, ...rest } = Astro.props
|
||||
---
|
||||
|
||||
<a
|
||||
href={href}
|
||||
target={external ? '_blank' : '_self'}
|
||||
class={cn(
|
||||
'inline-block transition-colors duration-300 ease-in-out',
|
||||
underline &&
|
||||
'underline decoration-muted-foreground underline-offset-[3px] hover:decoration-foreground',
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<slot />
|
||||
</a>
|
||||
```
|
||||
|
||||
We were able to, in a single helper function:
|
||||
|
||||
1. Concatenate whatever the user passed via the `class{:astro}` prop to our base styles
|
||||
2. Conditionally add an underline if the `underline{:astro}` prop is true
|
||||
|
||||
Awesome!
|
||||
|
||||
## Welcoming some UX features
|
||||
|
||||
Within the blog itself (as in the layout, appearance, and navigation) are features that I believe are essential for a great user experience:
|
||||
|
||||
- Images are awesome and, by default, your blog post should have an image associated with it as part of the post's [Open Graph](https://ogp.me/) metadata. Since you can do whatever you want with the image, all of my dummy posts will have a placeholder image placed within their folder in `src/content/blog/`. Whenever you load into a blog post, splat in the middle will be the image associated with that post in its frontmatter.
|
||||
- Theme selectors should be self-explanatory. I've added one on the top right of the header, which is also `sticky` and not `absolute` such that it doesn't ignore the document flow (and thus you won't have to add `mt-20` to the top of every single page).
|
||||
- The table of contents of a post shouldn't be reduced to a `<details closed>{:html}` at the start of a blog post on desktop. You'd need to go to the top of the page to navigate through items. I've added a sticky `<TableOfContents>{:html}` component which always hangs out around the unused left side margin of a blog post. I also attached a very tiny client-side script using [`IntersectionObserver{:js}`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) to highlight all of the headings you're viewing within the <abbr title="Table of Contents">TOC</abbr> as you scroll through the page—it also will handle nested headings in that the parent heading of a visible child will still be highlighted even if off-screen (see the dummy [2024 Post](/blog/2024-post) for an example of this). I'll still use a collapsible `<details>{:html}` element for the table of contents on mobile though since obviously a table of contents on the side is unfeasible for small screens.
|
||||
- Every page, except the homepage, will have a `<Breadcrumb>{:html}` component which shows you your current location in the site hierarchy. I don't see these often in blog templates even though they are so amazing for both discoverability (<abbr title="Search Engine Optimization">SEO</abbr> and crawling) and user experience (the user always knows how "deep" they are in the site).
|
||||
- You can specify multiple post authors via frontmatter. If this post author's ID is found within the `Authors` collection, then it will render particular info from that author's frontmatter file, `[author-name].md` (e.g. avatar, link to profile). For example, the previous post (2024 Post) has two authors: "enscribe" and "jktrn", where "enscribe" is the only author with a custom avatar since "jktrn" is unregistered.
|
||||
- Each author will have their own page, which lists all of their posts. If you're the only author throughout the entire blog then you can simply disregard all aspects regarding both inserting authors and the `Authors` collection.
|
||||
- Each tag will also have their own page, which lists all of the posts under that tag!
|
||||
- $\LaTeX$ is fully supported with [KaTeX](https://katex.org/):
|
||||
|
||||
<blockquote>
|
||||
|
||||
To solve the cubic equation $t^3 + pt + q = 0$ (where the real numbers
|
||||
$p, q$ satisfy ${4p^3 + 27q^2} > 0$) one can use Cardano's formula:
|
||||
|
||||
$$
|
||||
\sqrt[{3}]{
|
||||
-\frac{q}{2}
|
||||
+\sqrt{\frac{q^2}{4} + {\frac{p^{3}}{27}}}
|
||||
}+
|
||||
\sqrt[{3}]{
|
||||
-\frac{q}{2}
|
||||
-\sqrt{\frac{q^2}{4} + {\frac{p^{3}}{27}}}
|
||||
}
|
||||
$$
|
||||
|
||||
For any $u_1, \dots, u_n \in \mathbb{C}$ and
|
||||
$v_1, \dots, v_n \in \mathbb{C}$, the Cauchy–Bunyakovsky–Schwarz
|
||||
inequality can be written as follows:
|
||||
|
||||
$$
|
||||
\left| \sum_{k=1}^n {u_k \bar{v_k}} \right|^2
|
||||
\leq
|
||||
{
|
||||
\left( \sum_{k=1}^n {|u_k|} \right)^2
|
||||
\left( \sum_{k=1}^n {|v_k|} \right)^2
|
||||
}
|
||||
$$
|
||||
|
||||
Finally, the determinant of a Vandermonde matrix can be calculated
|
||||
using the following expression:
|
||||
|
||||
$$
|
||||
\begin{vmatrix}
|
||||
1 & x_1 & x_1^2 & \dots & x_1^{n-1} \\
|
||||
1 & x_2 & x_2^2 & \dots & x_2^{n-1} \\
|
||||
1 & x_3 & x_3^2 & \dots & x_3^{n-1} \\
|
||||
\vdots & \vdots & \vdots & \ddots & \vdots \\
|
||||
1 & x_n & x_n^2 & \dots & x_n^{n-1} \\
|
||||
\end{vmatrix}
|
||||
= {\prod_{1 \leq {i,j} \leq n} {(x_i - x_j)}}
|
||||
$$
|
||||
|
||||
—<cite>[Three famous mathematical formulas](https://developer.mozilla.org/en-US/docs/Learn/MathML/First_steps/Three_famous_mathematical_formulas) (Mozilla Docs)</cite>
|
||||
|
||||
</blockquote>
|
||||
|
||||
## Foregoing some slop
|
||||
|
||||
- Goodbye, [ESLint](https://eslint.org/)! There have been so many occasions where I've had to deal with blogging templates with in-built pre-commit hooks which enforce contrived and arbitrary linting rules that, frankly, I couldn't be bothered with. Obviously, linting is awesome for ensuring consistency and best practice, but that's for shared and large codebases. You're dealing with, at most, your MDX blog posts and some interior fetching. It's just not worth the headache.
|
||||
- You probably don't need analytics via [Umami](https://umami.is) or [Plausible](https://plausible.io). Let's be realistic: for many personal blogs, unless you're an anime profile picture Twitter microcelebrity, you don't need to know how many of your readers click Big Button A versus how many click Big Button B.
|
||||
- You likely don't need a comments section via [Giscus](https://giscus.app). This opens up a can of worms involving the ability to spam comments and the necessity to moderate them. If you want organic discussion about your blog posts to happen, then share on social media and let people discuss there.
|
||||
- Speaking of sharing on social media, let's get rid of the share buttons. When was the last time you actually used a share button on a blog post rather than just copying the URL?
|
||||
- You probably don't need a <abbr title="Content Management System">CMS</abbr> unless you have thousands of posts and/or are willing to navigate through a clunky management interface. Markdown and folders is really all you need, which you can organize to your preference via folder or file naming conventions.
|
||||
- If you have literally anything involving an `.env` file in a blogging site, maybe think about what you are doing for a moment.
|
||||
- Please consider not overriding the browser's <kbd>Ctrl</kbd> + <kbd>K</kbd> functionality to open up a command palette. There should not be a single reason why a user would use a small context menu to browse your blog over the `/blog` route. Most of the time, command palettes on sites do nothing more than regurgitate shortcuts that are already on the same page you're hiding with the palette's modal.
|
||||
|
||||
## Something important
|
||||
|
||||
Obviously a disclaimer: everything that I've shared here are my own personal gripes and, while I'd like for you to agree with me on a lot of these points for the better of the community, you can go ahead and disagree. The web development community, especially in spaces like Twitter and various online forums, is constantly engaged in heated debates about what constitutes "best practices." You'll find a wide spectrum of viewpoints:
|
||||
|
||||
1. Fundamentalists who adhere strictly to established patterns and completely disregard change,
|
||||
2. Accelerationists who eat up whatever Vercel cooks as if it's the second coming of Christ,
|
||||
3. and everyone in between this spectrum.
|
||||
|
||||
I wanted to share what particular technology stack worked the best for me in this particular use case. A stack for one project can be completely unusable for another. If you vehemently hate any of the design choices I've made then simply get rid of them. MIT license! Happy blogging.
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
name: 'Project A'
|
||||
description: 'This is an example project description! You should replace this with a description of your own project.'
|
||||
tags: ['Framework A', 'Library B', 'Tool C', 'Resource D']
|
||||
image: '../../../public/static/1200x630.png'
|
||||
link: 'https://example.com'
|
||||
---
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
name: 'Project B'
|
||||
description: 'This is an example project description! You should replace this with a description of your own project.'
|
||||
tags: ['Framework A', 'Library B', 'Tool C', 'Resource D']
|
||||
image: '../../../public/static/1200x630.png'
|
||||
link: 'https://example.com'
|
||||
---
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
name: 'Project C'
|
||||
description: 'This is an example project description! You should replace this with a description of your own project.'
|
||||
tags: ['Framework A', 'Library B', 'Tool C', 'Resource D']
|
||||
image: '../../../public/static/1200x630.png'
|
||||
link: 'https://example.com'
|
||||
---
|
|
@ -7,20 +7,15 @@ import { SITE } from '@/consts'
|
|||
type Props = {
|
||||
title: string
|
||||
description: string
|
||||
image?: string
|
||||
}
|
||||
|
||||
const { title, description, image } = Astro.props
|
||||
const { title, description } = Astro.props
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<Head
|
||||
title={`${title} | ${SITE.title}`}
|
||||
description={description}
|
||||
image={image}
|
||||
/>
|
||||
<Head title={`${title} | ${SITE.title}`} description={description} />
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
---
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
||||
import Container from '@/components/Container.astro'
|
||||
import Link from '@/components/Link.astro'
|
||||
import ProjectCard from '@/components/ProjectCard.astro'
|
||||
import { SITE } from '@/consts'
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
const projects = await getCollection('projects')
|
||||
---
|
||||
|
||||
<Layout title="About" description={SITE.description}>
|
||||
<Container class="flex flex-col gap-y-6">
|
||||
<Breadcrumbs items={[{ label: 'About', icon: 'lucide:info' }]} />
|
||||
|
||||
<section>
|
||||
<div class="min-w-full">
|
||||
<div class="prose mb-8">
|
||||
<p class="mb-4">
|
||||
astro-erudite is an opinionated, unstyled static blogging template
|
||||
that prioritizes simplicity and performance, built with <Link
|
||||
href="https://astro.build"
|
||||
class="text-foreground"
|
||||
external
|
||||
underline>Astro</Link
|
||||
>, <Link
|
||||
href="https://tailwindcss.com"
|
||||
class="text-foreground"
|
||||
external
|
||||
underline>Tailwind</Link
|
||||
>, and <Link
|
||||
href="https://ui.shadcn.com"
|
||||
class="text-foreground"
|
||||
external
|
||||
underline>shadcn/ui</Link
|
||||
>. It provides a clean foundation for your content while being
|
||||
extremely easy to customize.
|
||||
</p>
|
||||
<p>
|
||||
To learn more about the philosophy behind this template, check out
|
||||
the following blog post: <Link
|
||||
href="/blog/the-state-of-static-blogs"
|
||||
class="text-foreground"
|
||||
underline>The State of Static Blogs in 2024</Link
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="mb-4 text-2xl font-medium">Example Projects Listing</h2>
|
||||
<div class="flex flex-col gap-4">
|
||||
{projects.map((project) => <ProjectCard project={project} />)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Container>
|
||||
</Layout>
|
|
@ -1,65 +0,0 @@
|
|||
---
|
||||
import AuthorCard from '@/components/AuthorCard.astro'
|
||||
import BlogCard from '@/components/BlogCard.astro'
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
||||
import Container from '@/components/Container.astro'
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { type CollectionEntry, getCollection } from 'astro:content'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const authors = await getCollection('authors')
|
||||
return authors.map((author) => ({
|
||||
params: { id: author.id },
|
||||
props: { author },
|
||||
}))
|
||||
}
|
||||
|
||||
type Props = {
|
||||
author: CollectionEntry<'authors'>
|
||||
}
|
||||
|
||||
const { author } = Astro.props
|
||||
|
||||
const allPosts = await getCollection('blog')
|
||||
const authorPosts = allPosts
|
||||
.filter((post) => {
|
||||
return post.data.authors && post.data.authors.includes(author.id)
|
||||
})
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
|
||||
---
|
||||
|
||||
<Layout
|
||||
title={`${author.data.name} (Author)`}
|
||||
description={author.data.bio || `Profile of ${author.data.name}.`}
|
||||
>
|
||||
<Container class="flex flex-col gap-y-6">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ href: '/authors', label: 'Authors', icon: 'lucide:users' },
|
||||
{ label: author.data.name, icon: 'lucide:user' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<AuthorCard author={author} linkDisabled />
|
||||
</section>
|
||||
<section class="flex flex-col gap-y-4">
|
||||
<h2 class="text-2xl font-medium">Posts by {author.data.name}</h2>
|
||||
{
|
||||
authorPosts.length > 0 ? (
|
||||
<ul class="flex flex-col gap-4">
|
||||
{authorPosts.filter(post => !post.data.draft).map((post) => (
|
||||
<li>
|
||||
<BlogCard entry={post} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p class="text-muted-foreground">
|
||||
No posts available from this author.
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
</Container>
|
||||
</Layout>
|
|
@ -1,28 +0,0 @@
|
|||
---
|
||||
import AuthorCard from '@/components/AuthorCard.astro'
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
||||
import Container from '@/components/Container.astro'
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
const authors = await getCollection('authors')
|
||||
---
|
||||
|
||||
<Layout title="Authors" description="A list of authors on this site.">
|
||||
<Container class="flex flex-col gap-y-6">
|
||||
<Breadcrumbs items={[{ label: 'Authors', icon: 'lucide:users' }]} />
|
||||
{
|
||||
authors.length > 0 ? (
|
||||
<ul class="flex flex-col gap-4">
|
||||
{authors.map((author) => (
|
||||
<li>
|
||||
<AuthorCard author={author} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p class="text-muted-foreground text-center">No authors found.</p>
|
||||
)
|
||||
}
|
||||
</Container>
|
||||
</Layout>
|
|
@ -1,9 +1,7 @@
|
|||
---
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
||||
import Link from '@/components/Link.astro'
|
||||
import PostNavigation from '@/components/PostNavigation.astro'
|
||||
import TableOfContents from '@/components/TableOfContents.astro'
|
||||
import { badgeVariants } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
|
@ -54,11 +52,7 @@ const { Content, headings } = await render(post)
|
|||
const authors = await parseAuthors(post.data.authors ?? [])
|
||||
---
|
||||
|
||||
<Layout
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
image={post.data.image?.src ?? '/static/1200x630.png'}
|
||||
>
|
||||
<Layout title={post.data.title} description={post.data.description}>
|
||||
<section
|
||||
class="grid grid-cols-[minmax(0px,1fr)_min(var(--breakpoint-md),100%)_minmax(0px,1fr)] gap-y-6 *:px-4"
|
||||
>
|
||||
|
@ -89,62 +83,12 @@ const authors = await parseAuthors(post.data.authors ?? [])
|
|||
<div
|
||||
class="text-muted-foreground flex flex-wrap items-center justify-center gap-2 text-sm"
|
||||
>
|
||||
{
|
||||
authors.length > 0 && (
|
||||
<>
|
||||
<div class="flex items-center gap-x-2">
|
||||
{authors.map((author) => (
|
||||
<div class="flex items-center gap-x-1.5">
|
||||
<Image
|
||||
src={author.avatar}
|
||||
alt={author.name}
|
||||
width={24}
|
||||
height={24}
|
||||
class="rounded-full"
|
||||
/>
|
||||
{author.isRegistered ? (
|
||||
<Link
|
||||
href={`/authors/${author.id}`}
|
||||
underline
|
||||
class="text-foreground"
|
||||
>
|
||||
<span>{author.name}</span>
|
||||
</Link>
|
||||
) : (
|
||||
<span>{author.name}</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Separator orientation="vertical" className="h-4!" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{formatDate(post.data.date)}</span>
|
||||
<Separator orientation="vertical" className="h-4!" />
|
||||
<span>{readingTime(post.body!)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center gap-2">
|
||||
{
|
||||
post.data.tags && post.data.tags.length > 0 ? (
|
||||
post.data.tags.map((tag) => (
|
||||
<a
|
||||
href={`/tags/${tag}`}
|
||||
class={badgeVariants({ variant: 'secondary' })}
|
||||
>
|
||||
<Icon name="lucide:hash" class="size-3" />
|
||||
{tag}
|
||||
</a>
|
||||
))
|
||||
) : (
|
||||
<span class="text-muted-foreground text-sm">
|
||||
No tags available
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PostNavigation prevPost={prevPost} nextPost={nextPost} />
|
||||
|
|
|
@ -15,55 +15,6 @@ const blog = (await getCollection('blog'))
|
|||
|
||||
<Layout title="Home" description={SITE.description}>
|
||||
<Container class="flex flex-col gap-y-6">
|
||||
<section>
|
||||
<div class="rounded-lg border">
|
||||
<div class="flex flex-col space-y-1.5 p-6">
|
||||
<h3 class="text-3xl leading-none font-medium">er·u·dite</h3>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
/ˈer(y)əˌdīt/ • <span class="font-medium">adjective</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-6 pt-0">
|
||||
<p class="text-muted-foreground mb-2 text-sm">
|
||||
astro-erudite is an opinionated, unstyled static blogging template
|
||||
built with <Link
|
||||
href="https://astro.build"
|
||||
class="text-foreground"
|
||||
external
|
||||
underline>Astro</Link
|
||||
>, <Link
|
||||
href="https://tailwindcss.com"
|
||||
class="text-foreground"
|
||||
external
|
||||
underline>Tailwind</Link
|
||||
>, and <Link
|
||||
href="https://ui.shadcn.com"
|
||||
class="text-foreground"
|
||||
external
|
||||
underline>shadcn/ui</Link
|
||||
>. Extraordinarily loosely based on the <Link
|
||||
href="https://astro-micro.vercel.app/"
|
||||
class="text-foreground"
|
||||
external
|
||||
underline>Astro Micro</Link
|
||||
> theme.
|
||||
</p>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
To use this template, check out the <Link
|
||||
href="https://github.com/jktrn/astro-erudite"
|
||||
class="text-foreground"
|
||||
underline
|
||||
external>GitHub</Link
|
||||
> repository. To learn more about why this template exists, read this
|
||||
blog post: <Link
|
||||
href="/blog/the-state-of-static-blogs"
|
||||
class="text-foreground"
|
||||
underline>The State of Static Blogs in 2024</Link
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="flex flex-col gap-y-4">
|
||||
<h2 class="text-2xl font-medium">Latest posts</h2>
|
||||
<ul class="flex flex-col gap-y-4">
|
||||
|
|
|
@ -1,8 +1,52 @@
|
|||
import type { APIRoute } from 'astro'
|
||||
|
||||
const getRobotsTxt = (sitemapURL: URL) => `
|
||||
User-agent: *
|
||||
Allow: /
|
||||
User-agent: AI2Bot
|
||||
User-agent: Ai2Bot-Dolma
|
||||
User-agent: Amazonbot
|
||||
User-agent: anthropic-ai
|
||||
User-agent: Applebot
|
||||
User-agent: Applebot-Extended
|
||||
User-agent: Bytespider
|
||||
User-agent: CCBot
|
||||
User-agent: ChatGPT-User
|
||||
User-agent: Claude-Web
|
||||
User-agent: ClaudeBot
|
||||
User-agent: cohere-ai
|
||||
User-agent: cohere-training-data-crawler
|
||||
User-agent: Crawlspace
|
||||
User-agent: Diffbot
|
||||
User-agent: DuckAssistBot
|
||||
User-agent: FacebookBot
|
||||
User-agent: FriendlyCrawler
|
||||
User-agent: Google-Extended
|
||||
User-agent: GoogleOther
|
||||
User-agent: GoogleOther-Image
|
||||
User-agent: GoogleOther-Video
|
||||
User-agent: GPTBot
|
||||
User-agent: iaskspider/2.0
|
||||
User-agent: ICC-Crawler
|
||||
User-agent: ImagesiftBot
|
||||
User-agent: img2dataset
|
||||
User-agent: ISSCyberRiskCrawler
|
||||
User-agent: Kangaroo Bot
|
||||
User-agent: Meta-ExternalAgent
|
||||
User-agent: Meta-ExternalFetcher
|
||||
User-agent: OAI-SearchBot
|
||||
User-agent: omgili
|
||||
User-agent: omgilibot
|
||||
User-agent: PanguBot
|
||||
User-agent: PerplexityBot
|
||||
User-agent: PetalBot
|
||||
User-agent: Scrapy
|
||||
User-agent: SemrushBot-OCOB
|
||||
User-agent: SemrushBot-SWA
|
||||
User-agent: Sidetrade indexer bot
|
||||
User-agent: Timpibot
|
||||
User-agent: VelenPublicWebCrawler
|
||||
User-agent: Webzio-Extended
|
||||
User-agent: YouBot
|
||||
Disallow: /
|
||||
|
||||
Sitemap: ${sitemapURL.href}
|
||||
`
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
---
|
||||
import BlogCard from '@/components/BlogCard.astro'
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
||||
import Container from '@/components/Container.astro'
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import { type CollectionEntry, getCollection } from 'astro:content'
|
||||
|
||||
type BlogPost = CollectionEntry<'blog'>
|
||||
|
||||
type Props = {
|
||||
tag: string
|
||||
posts: BlogPost[]
|
||||
}
|
||||
|
||||
const { tag, posts } = Astro.props
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('blog')
|
||||
const tags = posts.flatMap((post) => post.data.tags || [])
|
||||
const uniqueTags = Array.from(
|
||||
new Set(tags.filter((tag): tag is string => typeof tag === 'string')),
|
||||
)
|
||||
|
||||
return uniqueTags.map((tag) => ({
|
||||
params: { id: tag },
|
||||
props: {
|
||||
tag,
|
||||
posts: posts.filter((post) => post.data.tags?.includes(tag)),
|
||||
},
|
||||
}))
|
||||
}
|
||||
---
|
||||
|
||||
<Layout
|
||||
title={`Posts tagged with "${tag}"`}
|
||||
description={`A collection of posts tagged with ${tag}.`}
|
||||
>
|
||||
<Container class="flex flex-col gap-y-6">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ href: '/tags', label: 'Tags', icon: 'lucide:tags' },
|
||||
{ label: tag, icon: 'lucide:tag' },
|
||||
]}
|
||||
/>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h1 class="text-3xl font-medium">Posts tagged with</h1>
|
||||
<span
|
||||
class="bg-secondary flex items-center gap-x-1 rounded-full px-4 py-2 text-2xl font-medium"
|
||||
>
|
||||
<Icon name="lucide:hash" class="size-6 -translate-x-0.5" />{tag}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-y-4">
|
||||
{
|
||||
posts.map((post) => (
|
||||
<section class="flex flex-col gap-y-4">
|
||||
<div>
|
||||
<ul class="flex flex-col gap-4">
|
||||
<li>
|
||||
<BlogCard entry={post} />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Container>
|
||||
</Layout>
|
|
@ -1,48 +0,0 @@
|
|||
---
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
||||
import Container from '@/components/Container.astro'
|
||||
import Link from '@/components/Link.astro'
|
||||
import { badgeVariants } from '@/components/ui/badge'
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
const blog = (await getCollection('blog')).filter((post) => !post.data.draft)
|
||||
|
||||
const tagCounts = blog.reduce((acc, post) => {
|
||||
post.data.tags?.forEach((tag) => {
|
||||
acc.set(tag, (acc.get(tag) || 0) + 1)
|
||||
})
|
||||
return acc
|
||||
}, new Map())
|
||||
|
||||
const tags = [...tagCounts.keys()].sort((a, b) => {
|
||||
const countDiff = tagCounts.get(b)! - tagCounts.get(a)!
|
||||
return countDiff !== 0 ? countDiff : a.localeCompare(b)
|
||||
})
|
||||
---
|
||||
|
||||
<Layout title="Tags" description="A list of all tags used in blog posts">
|
||||
<Container class="flex flex-col gap-y-6">
|
||||
<Breadcrumbs items={[{ label: 'Tags', icon: 'lucide:tags' }]} />
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{
|
||||
tags.map((tag) => (
|
||||
<Link
|
||||
href={`/tags/${tag}`}
|
||||
class={badgeVariants({ variant: 'secondary' })}
|
||||
>
|
||||
<Icon name="lucide:hash" class="size-3" />
|
||||
{tag}
|
||||
<span class="text-muted-foreground ml-1.5">
|
||||
({tagCounts.get(tag)})
|
||||
</span>
|
||||
</Link>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</Layout>
|
|
@ -4,12 +4,8 @@
|
|||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--font-sans:
|
||||
Geist, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
|
||||
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--font-mono:
|
||||
Geist Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
'Liberation Mono', 'Courier New', monospace;
|
||||
--font-sans: 'Geist Variable', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--font-mono: "Geist Mono Variable", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
||||
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
|
@ -36,22 +32,6 @@
|
|||
--color-ring: var(--ring);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist';
|
||||
src: url('/fonts/GeistVF.woff2') format('woff2-variations');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist Mono';
|
||||
src: url('/fonts/GeistMonoVF.woff2') format('woff2-variations');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
|
|