Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .prettierrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ module.exports = {
trailingComma: 'none',
printWidth: 100,
plugins: [require('prettier-plugin-svelte')],
overrides: [{ files: '*.svelte', options: { parser: 'svelte' } }]
overrides: [
{ files: '*.svelte', options: { parser: 'svelte' } },
{ files: '*.md', options: { useTabs: false, tabWidth: 2 } }
]
};
178 changes: 137 additions & 41 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,49 +138,137 @@ scripts/

**File Structure:**

- Blog posts are in `src/routes/blog/[post-slug]/` directories
- Each post has:
- `+page.svelte` - Main content (MDsveX)
- `+page.server.ts` - Server-side data loading
- `metadata.ts` - Post metadata export

**Front Matter (YAML):**
Required fields:

```yaml
---
title: 'Post Title'
date: '2024-01-15'
excerpt: 'Brief description for listings and SEO'
categories: ['Development', 'BitTorrent']
cover_image: '/images/posts/post-slug/cover.webp'
author: 'Author Name'
---
Blog posts live in `src/routes/blog/[post-slug]/`. Each post directory contains exactly three files:

| File | Purpose |
| ----------------- | -------------------------------------------------------------- |
| `metadata.ts` | Typed post metadata (title, date, tags, etc.) |
| `+page.server.ts` | Server-side data loader (identical boilerplate for every post) |
| `+page.svelte` | Post content written as a Svelte component |

**`metadata.ts` format:**

```typescript
export const metadata = {
title: 'Post Title',
slug: 'post-slug', // must match the directory name
contributor: 'Author Name',
contributorSlug: 'author-slug', // matches a directory under src/routes/contributor/
date: '2024-01-15T12:00:00.000Z', // ISO 8601
coverImage: '/images/posts/post-slug/cover.webp',
excerpt: 'Brief description for listings and SEO.',
tags: ['Rust', 'BitTorrent'] // title-case tags
};
```

Optional fields:
**`+page.server.ts` boilerplate** (copy verbatim for every post):

- `updated`: "2024-01-20" - Last update date
- `tags`: ["rust", "tracker"] - Additional tags
```typescript
import { getMetadata } from '$lib/data/metadata';
import type { PageServerLoad } from './$types';

**Workflow:**
export const load: PageServerLoad = async ({ url }) => {
const slug = url.pathname.split('/').filter(Boolean).pop();
if (!slug) throw new Error('Slug could not be determined.');

1. Use Front Matter VS Code extension for easier management
2. Create post directory: `src/routes/blog/my-new-post/`
3. Add front matter and content
4. Place images in `static/images/posts/my-new-post/`
5. Reference images: `/images/posts/my-new-post/image.png`
6. Use `<Image />` component for automatic optimization
7. Run `npm run dev` to preview
8. Metadata is auto-generated during build
const metadata = await getMetadata();
const currentPost = metadata.find((post) => post.slug === slug);

**Supported Content:**
if (!currentPost) throw new Error(`Post not found: ${slug}`);

- Standard Markdown syntax
- Svelte components inline
- Code blocks with syntax highlighting
- Images with automatic optimization
- Links with automatic external link handling
return { currentPost, allPosts: metadata };
};
```

**`+page.svelte` structure:**

```svelte
<script lang="ts">
import BlogPreview from '$lib/components/molecules/BlogPreview.svelte';
import Toc from '$lib/components/atoms/Toc.svelte';
import Post from '$lib/components/organisms/Post.svelte';
import PagesWrapper from '$lib/components/atoms/PagesWrapper.svelte';
import PrevNextPost from '$lib/components/singletons/PrevNextPost.svelte';
import Callout from '$lib/components/molecules/Callout.svelte';

let { data } = $props();
let currentPost = $derived(data.currentPost);
let allPosts = $derived(data.allPosts);
</script>

<Post
title={currentPost.title}
slug={currentPost.slug}
coverImage={currentPost.coverImage}
date={currentPost.date}
tags={currentPost.tags}
excerpt={currentPost.excerpt}
contributor={currentPost.contributor}
contributorSlug={currentPost.contributorSlug}
>
<PagesWrapper>
<div class="wrapper">
<Toc class="toc" />
<div id="toc-contents" class="content-preview">
<!-- Post content goes here -->
<h2 id="introduction">Introduction</h2>
<p>...</p>
</div>
</div>
</PagesWrapper>
<PrevNextPost currentPage={currentPost.slug} {allPosts} />
<div class="related-posts-container">
<h2>Related Posts:</h2>
<div class="grid">
{#each data.allPosts.slice(0, 3) as post}
<a href="/blog/{post.slug}">
<BlogPreview post_data={post} />
</a>
{/each}
</div>
</div>
</Post>

<style lang="scss">
@use '$lib/scss/breakpoints.scss' as bp;
/* styles here */
</style>
```

Look at an existing post (e.g. `src/routes/blog/vortex-rust-bittorrent-client-review/`) as a full reference implementation.

**Heading IDs for the Table of Contents:**

The `<Toc />` component auto-generates a table of contents from `<h2>` and `<h3>` elements inside the `id="toc-contents"` div. Every heading must have a matching `id` attribute:

```svelte
<h2 id="my-section">My Section</h2>
```

**Supported content components:**

| Component | Import | Usage |
| ------------- | -------------------------------------------- | ------------------------------------------------------------------------- |
| `<Callout>` | `$lib/components/molecules/Callout.svelte` | `<Callout type="info">...</Callout>` (types: `info`, `warning`, `danger`) |
| `<CodeBlock>` | `$lib/components/molecules/CodeBlock.svelte` | Fenced code with syntax highlighting |
| `<Image>` | `$lib/components/atoms/Image.svelte` | Optimised images (preferred over `<img>`) |

**Images:**

- Place images in `static/images/posts/my-new-post/`
- Reference them as `/images/posts/my-new-post/image.png`
- Use `<Image src="..." alt="..." />` for automatic WebP/AVIF optimisation
- Cover image should be named `cover.webp` and placed in the same folder

### ⚠️ Critical: regenerate metadata during development

`static/blogMetadata.json` drives the blog listing page and search. It is generated automatically by `npm run build`, but during development you must run it manually after adding or modifying a post's `metadata.ts`:

```bash
npx tsx scripts/generateMetadata.ts
```

Without this step the new post will not appear at `/blog`.

## Managing Contributors List

Expand Down Expand Up @@ -333,11 +421,19 @@ This metadata is used for:

### Adding a new blog post

1. Create a new `.md` file in `src/routes/blog/`
2. Add required front matter (title, date, excerpt, etc.)
3. Write content using Markdown and Svelte components as needed
4. Add cover image to `static/images/blog/`
5. Test locally with `npm run dev`
1. Create a new directory: `src/routes/blog/my-new-post/`
2. Add `metadata.ts` with required fields (see **Managing Blog Posts** above)
3. Add `+page.server.ts` (copy boilerplate verbatim from any existing post)
4. Add `+page.svelte` with post content (use an existing post as a reference)
5. Place cover image at `static/images/posts/my-new-post/cover.webp`
6. Regenerate metadata so the post appears in the listing:

```bash
npx tsx scripts/generateMetadata.ts
```

7. Run `npm run dev` and verify the post appears at `http://localhost:5173/blog`
8. Run `npm run check` and `npm run lint` before committing

### Adding a new component

Expand Down
5 changes: 5 additions & 0 deletions project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ bencoded
Bencoding
binstall
bitmagnet
bitorrent
Bragilevsky
Bram
buildtime
Expand Down Expand Up @@ -83,6 +84,8 @@ nojekyll
nologin
Nuxt
opentracker
optimisation
Optimised
passcode
pataquets
pcarles
Expand Down Expand Up @@ -118,10 +121,12 @@ TMDB
tornnab
torrenting
Torrust
Torshare
Torznab
Troubleshoting
Ttorrent
Tzou
UDPT
usermod
valgrind
Verstappen
Expand Down
2 changes: 1 addition & 1 deletion src/lib/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ export const trackerTableData = [
language: 'Rust',
os: 'Linux,MacOs,Windows',
repo: 'https://github.com/Power2All/torrust-actix',
demo: 'https://www.gbitt.info'
demo: 'https://www.torrust-actix.com/'
},
{
name: 'opentracker',
Expand Down
14 changes: 14 additions & 0 deletions src/routes/blog/trackers-implemented-in-rust/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getMetadata } from '$lib/data/metadata';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ url }) => {
const slug = url.pathname.split('/').filter(Boolean).pop();
if (!slug) throw new Error('Slug could not be determined.');

const metadata = await getMetadata();
const currentPost = metadata.find((post) => post.slug === slug);

if (!currentPost) throw new Error(`Post not found: ${slug}`);

return { currentPost, allPosts: metadata };
};
Loading