Compare commits

...

8 Commits

Author SHA1 Message Date
Julien Calixte
aeb763dd43 chore: optimize images 2026-05-24 00:54:53 +02:00
Julien Calixte
50d8eb31d6 fix(nginx): redirect unknown paths to homepage
Drop the $uri/ directory step in try_files which triggered the default
403 page on directories without an index, and 302 any 403/404 back to /.
2026-05-23 16:52:12 +02:00
Julien Calixte
9e1fae4f2e feat(pub): add /pub index listing all notes 2026-05-23 16:52:08 +02:00
Julien Calixte
d0175025ad feat(home): show only last 15 notes with link to /pub 2026-05-23 16:52:04 +02:00
Julien Calixte
3153200eb3 refactor(notes): sort fetched notes by publishedAt desc
Centralise ordering in fetchNotes so every consumer (homepage, /pub
index, RSS feed) shares the same newest-first order.
2026-05-23 16:52:01 +02:00
Julien Calixte
ffad0ecf54 docs: describe atproto-backed /pub route 2026-05-23 16:13:50 +02:00
Julien Calixte
71ad6bbc2e docs: rewrite README for apoena.dev 2026-05-23 16:12:56 +02:00
Julien Calixte
aa36e00576 fix: use plc.directory instead of plc.wtf for DID resolution
The plc.wtf mirror is returning HTTP 502, breaking the static build
when getStaticPaths fetches the PDS endpoint. plc.directory is the
canonical PLC directory and returns the same JSON shape.
2026-05-23 16:07:09 +02:00
20 changed files with 58 additions and 48 deletions

View File

@@ -1,50 +1,32 @@
# blog
# apoena.dev
[îles]: https://github.com/ElMassimo/iles
[configuration reference]: https://iles-docs.netlify.app/config
Julien Calixte's blog, built with [îles](https://github.com/ElMassimo/iles).
This template should help get you started developing with [îles].
Live at [apoena.dev](https://apoena.dev).
## Recommended IDE Setup
## Requirements
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) (and disable Vetur).
- Node.js >= 22
- pnpm 10
## Type Support for `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic `PageComponent` type by default. In most cases this is fine if you don't really care about component prop types outside of templates.
However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can run `Volar: Switch TS Plugin on/off` from VSCode command palette.
## Customize configuration
See îles [Configuration Reference].
## Project Setup
## Scripts
```sh
npm install
pnpm install # install dependencies
pnpm dev # start the dev server
pnpm build # build for production
pnpm preview # preview the production build on port 5050
pnpm typecheck # run vue-tsc
```
### Compile and Hot-Reload for Development
## AT Protocol notes (`/pub`)
```sh
npm run dev
```
Pages under `/pub/:rkey/:slug` are generated at build time from records
published to my AT Protocol repository.
### Type-Check, Compile and Minify for Production
- DID: `did:plc:4m3kouplb7s7xozjd3whinvl`
- Collection: `site.standard.document`
- PDS endpoint is resolved via [plc.directory](https://plc.directory),
then records are listed with `com.atproto.repo.listRecords`.
```sh
npm run build
```
### Run Unit Tests with [Cypress Component Testing](https://docs.cypress.io/guides/component-testing/introduction)
```sh
npm run test:unit # or `npm run test:unit:ci` for headless testing
```
### Run End-to-End Tests with [Cypress](https://www.cypress.io/)
```sh
npm run test:e2e # or `npm run test:e2e:ci` for headless testing
```
See `src/api/fetch-notes.ts` and `src/pages/pub/[rkey]/[slug].vue`.

View File

@@ -4,7 +4,7 @@ server {
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri.html $uri/index.html $uri/index.htm $uri/ =404;
try_files $uri $uri.html $uri/index.html $uri/index.htm =404;
}
location /.well-known/ {
@@ -13,11 +13,7 @@ server {
add_header Access-Control-Allow-Methods "GET, OPTIONS";
}
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
internal;
}
error_page 403 404 =302 /;
error_page 500 502 503 504 /50x.html;
location = /50x.html {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -8,7 +8,7 @@ export const getServiceEndpoint = async () => {
if (serviceEndpoint) {
return serviceEndpoint
}
const response = await fetch(`https://plc.wtf/${did}`)
const response = await fetch(`https://plc.directory/${did}`)
const {
service: [{ serviceEndpoint: endpoint }],
} = await response.json()
@@ -34,5 +34,8 @@ export const fetchNotes = async (): Promise<Note[]> => {
limit: 50,
})
return (records || []).map((record) => record.value)
const notes = (records || []).map((record) => record.value as Note)
return notes.sort((a, b) =>
a.publishedAt < b.publishedAt ? 1 : a.publishedAt > b.publishedAt ? -1 : 0,
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 B

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 900 B

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,16 +1,17 @@
<script setup lang="ts">
import { fetchNotes } from "@/api/fetch-notes"
const notes = await fetchNotes()
const notes = (await fetchNotes()).slice(0, 15)
</script>
<template>
<div class="blog-notes" v-if="notes.length > 0">
<h2>Last notes</h2>
<ul>
<li v-for="note in notes" :key="note.href">
<li v-for="note in notes" :key="note.path">
<a :href="note.path">{{ note.title }}</a>
</li>
</ul>
<p><a href="/pub">See all articles </a></p>
</div>
</template>

28
src/pages/pub/index.vue Normal file
View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
import { fetchNotes } from "@/api/fetch-notes"
const notes = await fetchNotes()
useHead({
title: "Articles",
})
</script>
<template>
<section class="pub-list">
<h1>Articles</h1>
<ul>
<li v-for="note in notes" :key="note.path">
<a :href="note.path">{{ note.title }}</a>
</li>
</ul>
</section>
</template>
<style scoped lang="scss">
.pub-list {
max-width: 800px;
margin: auto;
padding: 1rem;
}
</style>