summaryrefslogtreecommitdiff
path: root/src/pages
diff options
context:
space:
mode:
authorJoão Augusto Costa Branco Marado Torres <torres.dev@disroot.org>2025-06-28 18:14:22 -0300
committerJoão Augusto Costa Branco Marado Torres <torres.dev@disroot.org>2025-06-28 18:14:22 -0300
commit79fd506d30eef3d113f4a8e3ab9ebd9004f1e8cc (patch)
tree96ff57c92e897c3cc3331e23043d20f1665c7d0a /src/pages
parenta1eac976b20e39f86d5944fbec68e2a0f8ffb746 (diff)
feat: index page
Signed-off-by: João Augusto Costa Branco Marado Torres <torres.dev@disroot.org>
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/blog/[...year].astro30
-rw-r--r--src/pages/blog/micro/[page].astro32
-rw-r--r--src/pages/blog/read/[...slug].astro74
-rw-r--r--src/pages/index.astro104
-rw-r--r--src/pages/robots.txt.ts4
-rw-r--r--src/pages/rss.xml.js16
-rw-r--r--src/pages/rss.xml.ts32
7 files changed, 214 insertions, 78 deletions
diff --git a/src/pages/blog/[...year].astro b/src/pages/blog/[...year].astro
index f148a76..1742baa 100644
--- a/src/pages/blog/[...year].astro
+++ b/src/pages/blog/[...year].astro
@@ -1,20 +1,16 @@
---
+import type {
+ GetStaticPaths,
+ InferGetStaticParamsType,
+ InferGetStaticPropsType,
+} from "astro";
import { getCollection } from "astro:content";
-import type { CollectionEntry } from "astro:content";
import Base from "@layouts/Base.astro";
import DateSelector from "@components/DateSelector.astro";
import BlogCard from "@components/BlogCard.astro";
+import { sortLastCreated } from "@lib/collection/helpers";
-type Props = {
- posts: CollectionEntry<"blog">[];
- next: string;
- previous: string;
- years: number[];
- months: number[];
- days?: number[];
-};
-
-export async function getStaticPaths() {
+export const getStaticPaths = (async () => {
const posts = await getCollection("blog");
const archive = {
@@ -128,7 +124,7 @@ export async function getStaticPaths() {
paths.push({
params: { year: ymd },
props: {
- posts: archive.postsByDate.get(ymd),
+ posts: archive.postsByDate.get(ymd) ?? [],
next: archive.sortedDates?.[i + 1],
previous: archive.sortedDates?.[i - 1],
years: sortedYears,
@@ -139,16 +135,16 @@ export async function getStaticPaths() {
}
return paths;
-}
+}) satisfies GetStaticPaths;
+
+export type Params = InferGetStaticParamsType<typeof getStaticPaths>;
+export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
const title = "Blog";
const description = "Latest articles.";
let { posts, previous, next, years, months, days } = Astro.props;
-posts = posts.sort((a, b) =>
- new Date(b.data.dateCreated).valueOf() -
- new Date(a.data.dateCreated).valueOf()
-);
+posts = posts.sort(sortLastCreated);
const date = posts[0].data.dateCreated as Date;
---
diff --git a/src/pages/blog/micro/[page].astro b/src/pages/blog/micro/[page].astro
new file mode 100644
index 0000000..9fb04f1
--- /dev/null
+++ b/src/pages/blog/micro/[page].astro
@@ -0,0 +1,32 @@
+---
+import MicroBlog from "@components/templates/MicroBlog.astro";
+import Base from "@layouts/Base.astro";
+import { fromPosts, isMicro } from "@lib/collection/helpers";
+import { identity } from "@utils/anonymous";
+import type {
+ GetStaticPaths,
+ InferGetStaticParamsType,
+ InferGetStaticPropsType,
+} from "astro";
+
+export const getStaticPaths = (async ({ paginate }) => {
+ const micros = await fromPosts(isMicro, identity);
+
+ return paginate(micros, { pageSize: 20 });
+}) satisfies GetStaticPaths;
+
+export type Params = InferGetStaticParamsType<typeof getStaticPaths>;
+export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
+
+const { page } = Astro.props;
+---
+<Base title="Micro Blogue">
+ <h1>Page {page.currentPage}</h1>
+ <ul>
+ {page.data.map((micro) => <li><MicroBlog {...micro} /></li>)}
+ </ul>
+ {page.url.first ? <a href={page.url.first}>First</a> : null}
+ {page.url.prev ? <a href={page.url.prev}>Previous</a> : null}
+ {page.url.next ? <a href={page.url.next}>Next</a> : null}
+ {page.url.last ? <a href={page.url.last}>Last</a> : null}
+</Base>
diff --git a/src/pages/blog/read/[...slug].astro b/src/pages/blog/read/[...slug].astro
index 05d68e8..348a976 100644
--- a/src/pages/blog/read/[...slug].astro
+++ b/src/pages/blog/read/[...slug].astro
@@ -9,9 +9,9 @@ import Keywords from "@components/Keywords.astro";
import Citations from "@components/Citations.astro";
import Signature from "@components/signature/Signature.astro";
import CopyrightNotice from "@components/CopyrightNotice.astro";
-import { getEntries } from "astro:content";
import { verifier as verifierPrototype } from "@lib/pgp/verify";
-import { defined, get } from "@utils/anonymous";
+import { getSigners } from "@lib/collection/helpers";
+import { get } from "@utils/anonymous";
import Authors from "@components/signature/Authors.astro";
import { getEntry } from "astro:content";
@@ -27,8 +27,9 @@ type Props = CollectionEntry<"blog">;
const post = Astro.props;
-if (defined(post.data.translationOf)) {
- const original = await getEntry(
+let original: CollectionEntry<"blog">;
+if (post.data.kind === "translation") {
+ original = await getEntry(
post.data.translationOf as CollectionEntry<"blog">,
);
@@ -40,15 +41,15 @@ if (defined(post.data.translationOf)) {
(s) => s.role === "author",
).map((s) => s.entity.id)?.[0];
const originalCoAuthors = new Set(
- (original.data.signer ?? []).filter(
+ (original.data.signers ?? []).filter(
(s) => s.role === "co-author",
).map((s) => s.entity.id),
);
- const translationAuthor = (post.data.signer ?? []).filter(
+ const translationAuthor = (post.data.signers ?? []).filter(
(s) => s.role === "author",
).map((s) => s.entity.id)?.[0];
const translationCoAuthors = new Set(
- (post.data.signer ?? []).filter(
+ (post.data.signers ?? []).filter(
(s) => s.role === "co-author",
).map((s) => s.entity.id),
);
@@ -63,7 +64,7 @@ if (defined(post.data.translationOf)) {
);
}
- const translators = (post.data.signer ?? []).filter(
+ const translators = (post.data.signers ?? []).filter(
(s) => s.role === "translator",
).map((s) => s.entity.id);
@@ -77,7 +78,8 @@ if (defined(post.data.translationOf)) {
}
}
} else {
- if (post.data.signer?.some((x) => x.role === "translator")) {
+ original = post;
+ if (post.data.signers?.some((x) => x.role === "translator")) {
throw new Error(
`Post ${post.id} is not a translation but has translators defined`,
);
@@ -89,8 +91,8 @@ const translationsSet = new Set(
(await getCollection(
"blog",
(x) =>
- x.data.translationOf?.id ===
- (post.data.translationOf !== undefined
+ (x.data.kind === "translation") && x.data.translationOf.id ===
+ (post.data.kind === "translation"
? post.data.translationOf.id
: post.id),
) ?? []).map(({ id }) => id),
@@ -102,16 +104,7 @@ const translations = [...translationsSet.values()].map((id) => ({
id,
}));
-const signers = await getEntries(
- post.data.signer?.map(get("entity")) ?? [],
-).then((x) => x.filter(defined))
- .then((x) =>
- x.map((x) => ({
- entity: x,
- role: post.data.signer?.find((y) => y.entity.id === x.id)?.role,
- }))
- )
- .then((x) => x.filter((x) => x.role !== undefined));
+const signers = await getSigners(post);
const verifier = await verifierPrototype.then((x) => x.clone());
@@ -137,7 +130,12 @@ const commit = await verification?.commit;
<html lang="pt-PT">
<head>
- <BaseHead title={post.data.title} description={post.data.description} />
+ <BaseHead
+ title={post.data.title}
+ description={"description" in post.data
+ ? post.data.description
+ : post.data.title}
+ />
</head>
<body>
@@ -151,7 +149,7 @@ const commit = await verification?.commit;
<hgroup>
<h1 itemprop="headline">{post.data.title}</h1>
{
- post.data.subtitle && (
+ "subtitle" in post.data && (
<p itemprop="alternativeHeadline" class="subtitle">
{post.data.subtitle}
</p>
@@ -159,7 +157,8 @@ const commit = await verification?.commit;
}
</hgroup>
{
- post.data.description && (
+ "description" in post.data && post.data.description &&
+ (
<section itemprop="abstract">
<h2>Resumo</h2>
{
@@ -181,7 +180,7 @@ const commit = await verification?.commit;
<Authors
verifications={verification.verifications}
expectedSigners={signers}
- commitSignerKey={commit?.signature?.keyFingerPrint}
+ commitSignerKey={commit?.signature?.signer}
/>
)
}
@@ -201,7 +200,7 @@ const commit = await verification?.commit;
post.data.dateUpdated && (
<dt>Última atualização</dt><dd>
<time
- itemprop="dateUpdated"
+ itemprop="dateModified"
datetime={toIso8601Full(post.data.dateUpdated)}
>{
new Intl.DateTimeFormat([lang], {}).format(
@@ -212,7 +211,8 @@ const commit = await verification?.commit;
)
}
{
- post.data.locationCreated && (
+ "locationCreated" in post.data &&
+ post.data.locationCreated && (
<dt
itemprop="locationCreated"
itemscope
@@ -230,15 +230,23 @@ const commit = await verification?.commit;
<hr />
<div itemprop="articleBody text"><Content /></div>
<hr />
- <Keywords keywords={post.data.keywords} />
- <Citations citations={post.data.relatedPosts} />
+ {
+ "keywords" in original.data && (
+ <Keywords keywords={original.data.keywords} />
+ )
+ }
+ {
+ "relatedPosts" in original.data && (
+ <Citations citations={original.data.relatedPosts} />
+ )
+ }
<CopyrightNotice
- author={signers[0]?.entity.data.website?.[0] ?? "Anonymous"}
- website={signers[0]?.entity.data.website?.[0]}
- email={signers[0]?.entity.data.website?.[0]}
+ author={signers[0]?.entity.data.websites?.[0] ?? "Anonymous"}
+ website={signers[0]?.entity.data.websites?.[0]}
+ email={signers[0]?.entity.data.websites?.[0]}
title={post.data.title}
dateCreated={post.data.dateCreated}
- license={post.data.license as License}
+ license={post.data.license}
/>
</article>
</main>
diff --git a/src/pages/index.astro b/src/pages/index.astro
index eea5205..7e506bd 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1,28 +1,112 @@
---
+import MicroBlog from "@components/templates/MicroBlog.astro";
+import SimplePostList from "@components/templates/SimplePostList.astro";
import Base from "@layouts/Base.astro";
-import { SITE_TITLE } from "src/consts";
+import {
+ fromPosts,
+ isMicro,
+ isOriginal,
+ sortLastUpdated,
+} from "@lib/collection/helpers";
+import { env } from "@lib/env";
+
+const { PUBLIC_SITE_TITLE } = env;
+
+const originals = await fromPosts(
+ isOriginal,
+ (originals) => originals.sort(sortLastUpdated).slice(0, 10),
+);
+const micro = await fromPosts(
+ isMicro,
+ (originals) => originals.sort(sortLastUpdated)?.[0],
+);
---
-<Base title={SITE_TITLE} showSearch={true} showNav={true}>
+<Base title={PUBLIC_SITE_TITLE}>
<main>
<article>
<h2>Viva abril!</h2>
<figure>
<blockquote lang="es-VE" translate="no">
- &laquo;Los que le cierran el camino a la revoluci&oacute;n
- pac&iacute;fica le abren al mismo tiempo el camino a la
- revoluci&oacute;n violenta&raquo;.
+ <i>«Los que le cierran el camino a la revolución pacífica le abren al
+ mismo tiempo el camino a la revolución violenta.»</i>
</blockquote>
<figcaption>
- &mdash; Hugo Ch&aacute;vez.
+ &mdash; Hugo Chávez.
<p>
- Tradu&ccedil;&atilde;o: &ldquo;Aqueles que fecham o caminho para a
- revolu&ccedil;&atilde;o pac&iacute;fica abrem, ao mesmo tempo, o
- caminho para a revolu&ccedil;&atilde;o violenta.&rdquo;
+ <small>Tradução: &ldquo;Aqueles que fecham o caminho para a
+ revolução pacífica abrem, ao mesmo tempo, o caminho para a
+ revolução violenta&rdquo;.</small>
</p>
</figcaption>
</figure>
- <p><em>Portugal <em>fez</em> diferente!</em></p>
+ <p class="lead"><em>Portugal <em>fez</em> diferente!</em></p>
</article>
+ {
+ (originals.length > 0 || micro) && (
+ <section id="posts">
+ <h2>Últimas aplicações atualizadas</h2>
+ {micro && <div id="last-micro"><MicroBlog {...micro} /></div>}
+ <div id="last-originals"><SimplePostList posts={originals} /></div>
+ </section>
+ )
+ }
</main>
</Base>
+
+<style>
+ figure:has(blockquote) {
+ border-inline-start: 2px solid var(--color-active);
+ padding-inline-start: calc(var(--size-7) * 1em);
+
+ & > blockquote {
+ margin-block-start: calc(var(--size-7) * 1em);
+ margin-inline-start: 0;
+ border-inline-start: 2px solid var(--color-light);
+ padding-inline-start: calc(var(--size-7) * 1em);
+ }
+ }
+
+ #posts {
+ position: relative;
+
+ & > h2 {
+ float: inline-start;
+ }
+ }
+
+ #last-micro {
+ clear: inline-start;
+ max-width: 40ch;
+ margin-inline: auto;
+ }
+
+ #last-originals {
+ clear: inline-start;
+ }
+
+ @media (width >= 30rem) {
+ #posts {
+ & > h2 {
+ float: inline-start;
+ max-width: calc(
+ 100svw
+ - calc(
+ 50svw
+ + calc(
+ calc(2 * calc(var(--size-4) * 1em)) + calc(var(--size-2) * 1em)
+ )
+ )
+ );
+ }
+ }
+
+ #last-micro {
+ clear: none;
+ float: inline-end;
+ width: 50svw;
+ margin-inline-start: calc(var(--size-2) * 1em);
+ margin-block-end: calc(var(--size-2) * 1em);
+ }
+ }
+</style>
diff --git a/src/pages/robots.txt.ts b/src/pages/robots.txt.ts
index 4edef8b..78c9fdf 100644
--- a/src/pages/robots.txt.ts
+++ b/src/pages/robots.txt.ts
@@ -1,4 +1,4 @@
-import type { APIRoute } from "astro";
+import type { APIContext, APIRoute } from "astro";
const getRobotsTxt = (sitemapURL: URL) => `
User-agent: *
@@ -7,7 +7,7 @@ Allow: /
Sitemap: ${sitemapURL.href}
`;
-export const GET: APIRoute = ({ site }) => {
+export const GET: APIRoute = ({ site }: APIContext): Response => {
const sitemapURL = new URL("sitemap-index.xml", site);
return new Response(getRobotsTxt(sitemapURL));
};
diff --git a/src/pages/rss.xml.js b/src/pages/rss.xml.js
deleted file mode 100644
index de5685b..0000000
--- a/src/pages/rss.xml.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import rss from "@astrojs/rss";
-import { getCollection } from "astro:content";
-import { SITE_DESCRIPTION, SITE_TITLE } from "../consts";
-
-export async function GET(context) {
- const posts = await getCollection("blog");
- return rss({
- title: SITE_TITLE,
- description: SITE_DESCRIPTION,
- site: context.site,
- items: posts.map((post) => ({
- ...post.data,
- link: `/blog/${post.id}/`,
- })),
- });
-}
diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts
new file mode 100644
index 0000000..c07f3bd
--- /dev/null
+++ b/src/pages/rss.xml.ts
@@ -0,0 +1,32 @@
+import rss, { type RSSFeedItem } from "@astrojs/rss";
+import { getCollection } from "astro:content";
+import type { APIContext, APIRoute } from "astro";
+import { Blog } from "../lib/collection/schemas.ts";
+import { getFirstAuthorEmail } from "../lib/collection/helpers.ts";
+import { env } from "../lib/env.ts";
+
+const { PUBLIC_SITE_TITLE, PUBLIC_SITE_DESCRIPTION, PUBLIC_SITE_URL } = env;
+
+export const GET: APIRoute = async (context: APIContext): Promise<Response> => {
+ const posts = await getCollection("blog");
+ return rss({
+ title: PUBLIC_SITE_TITLE,
+ description: PUBLIC_SITE_DESCRIPTION,
+ site: context.site ?? PUBLIC_SITE_URL,
+ items: await Promise.all(posts.map(async (post): Promise<RSSFeedItem> => {
+ const { id, rendered } = post;
+ const blog = Blog.parse(post.data);
+
+ const { title, dateUpdated, dateCreated } = blog;
+ return {
+ description: "description" in blog ? blog.description : undefined,
+ title,
+ author: await getFirstAuthorEmail(post),
+ content: rendered?.html,
+ pubDate: dateUpdated ?? dateCreated,
+ categories: "keywords" in blog ? blog.keywords : undefined,
+ link: `/blog/read/${id}/`,
+ };
+ })),
+ });
+};