diff options
author | João Augusto Costa Branco Marado Torres <torres.dev@disroot.org> | 2025-07-21 00:37:34 -0300 |
---|---|---|
committer | João Augusto Costa Branco Marado Torres <torres.dev@disroot.org> | 2025-07-21 00:37:34 -0300 |
commit | c530d30a74bb521b1c22f0e2a468d9b69c6a2e8e (patch) | |
tree | c9230fdf28aa8865ac2066450b84009bc9da0168 /src/components | |
parent | 25375fccfe4477022c0b354c6e1018ff5a07df95 (diff) |
feat: blog listing style
Signed-off-by: João Augusto Costa Branco Marado Torres <torres.dev@disroot.org>
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/DateSelector.astro | 172 | ||||
-rw-r--r-- | src/components/templates/SimplePostList.astro | 91 |
2 files changed, 171 insertions, 92 deletions
diff --git a/src/components/DateSelector.astro b/src/components/DateSelector.astro index d57919e..ab10fd1 100644 --- a/src/components/DateSelector.astro +++ b/src/components/DateSelector.astro @@ -1,6 +1,8 @@ --- +import { defined } from "@utils/anonymous"; + interface Props { - date: Date; + date?: Date; years: number[]; months: number[]; days?: number[]; @@ -8,9 +10,10 @@ interface Props { const { date, years, months, days } = Astro.props; -const y = date.getFullYear(); -const m = date.getMonth() + 1; -const d = date.getDate(); +const dateParts = Astro.params.date?.split("/").map(Number); +const y = dateParts?.[0]; +const m = dateParts?.[1]; +const d = dateParts?.[2]; let yI = 0; let mI = 0; let dI = 0; @@ -19,23 +22,31 @@ const list = new Intl.ListFormat("pt-PT", { type: "unit", style: "narrow" }); const pad = (n: number) => String(n).padStart(2, "0"); --- -<nav> +<nav class="mute small"> <span role="list"> Anos:{" "} { - list.formatToParts(years.map((y) => - new Intl.DateTimeFormat("pt-PT", { year: "2-digit" }).format( - new Date( - Date.UTC( - y, - 0, - 1, - date.getTimezoneOffset() / 60, - date.getTimezoneOffset() % 60, + list.formatToParts( + years.sort().map((y) => + new Intl.DateTimeFormat("pt-PT", { year: "numeric" }).format( + new Date( + Date.UTC( + y, + 0, + 3, + ...[ + date === undefined + ? undefined + : date.getTimezoneOffset() / 60, + date === undefined + ? undefined + : date.getTimezoneOffset() % 60, + ].filter(defined), + ), ), - ), - ) - )).map(({ type, value }: { type: string; value: string }) => { + ) + ), + ).map(({ type, value }: { type: string; value: string }) => { switch (type) { case "element": { const year = years[yI++]; @@ -53,67 +64,39 @@ const pad = (n: number) => String(n).padStart(2, "0"); }) } </span> - <br /> - <span role="list"> - Meses:{" "} - { - list.formatToParts(months.map((m) => - new Intl.DateTimeFormat("pt-PT", { month: "short" }).format( - new Date( - Date.UTC( - y, - m - 1, - 1, - date.getTimezoneOffset() / 60, - date.getTimezoneOffset() % 60, - ), - ), - ) - )).map(({ type, value }: { type: string; value: string }) => { - switch (type) { - case "element": { - const month = months[mI++]; - return ( - <span role="listitem"><a - class:list={[{ active: month === m }]} - href={`/blog/${y}/${pad(month)}`} - >{value}</a></span> - ); - } - case "literal": { - return <span>{value}</span>; - } - } - }) - } - </span> { - days && - ( + y && ( <><br /><span role="list"> - Dias:{" "} + Meses:{" "} { - list.formatToParts(days.map((d) => { - return new Intl.DateTimeFormat("pt-PT", { day: "numeric" }) - .format( + list.formatToParts( + months.sort().map((m) => + new Intl.DateTimeFormat("pt-PT", { month: "short" }).format( new Date( Date.UTC( y, m - 1, - d, - date.getTimezoneOffset() / 60, - date.getTimezoneOffset() % 60, + 3, + ...[ + date === undefined + ? undefined + : date.getTimezoneOffset() / 60, + date === undefined + ? undefined + : date.getTimezoneOffset() % 60, + ].filter(defined), ), ), - ); - })).map(({ type, value }: { type: string; value: string }) => { + ) + ), + ).map(({ type, value }: { type: string; value: string }) => { switch (type) { case "element": { - const day = days[dI++]; + const month = months[mI++]; return ( <span role="listitem"><a - class:list={[{ active: day === d }]} - href={`/blog/${y}/${pad(m)}/${pad(d)}`} + class:list={[{ active: month === m }]} + href={`/blog/${y}/${pad(month)}`} >{value}</a></span> ); } @@ -126,10 +109,69 @@ const pad = (n: number) => String(n).padStart(2, "0"); </span></> ) } + { + days && + ( + <><br /><span role="list"> + Dias:{" "} + { + y && m && + list.formatToParts( + days.sort().map((d) => { + return new Intl.DateTimeFormat("pt-PT", { + day: "numeric", + }) + .format( + new Date( + Date.UTC( + y, + m - 1, + d, + ...[ + date === undefined + ? undefined + : date.getTimezoneOffset() / 60, + date === undefined + ? undefined + : date.getTimezoneOffset() % 60, + ].filter(defined), + ), + ), + ); + }), + ).map( + ({ type, value }: { type: string; value: string }) => { + switch (type) { + case "element": { + const day = days[dI++]; + return ( + <span role="listitem"><a + class:list={[{ active: day === d }]} + href={`/blog/${y}/${pad(m)}/${pad(day)}`} + >{value}</a></span> + ); + } + case "literal": { + return <span>{value}</span>; + } + } + }, + ) + } + </span></> + ) + } </nav> <style> + nav { + padding-block: calc(var(--size-2) * 1em); + } a.active { font-weight: bolder; } + + a:hover { + color: var(--color-active); + } </style> diff --git a/src/components/templates/SimplePostList.astro b/src/components/templates/SimplePostList.astro index 164b32b..6d05a1f 100644 --- a/src/components/templates/SimplePostList.astro +++ b/src/components/templates/SimplePostList.astro @@ -1,25 +1,45 @@ --- import Date from "@components/organisms/Date.astro"; import KeywordsList from "@components/organisms/KeywordsList.astro"; -import { getFirstUserID, getLastUpdate } from "@lib/collection/helpers"; -import type { Original } from "@lib/collection/schemas"; -import type { z } from "astro:content"; +import { + getFirstUserID, + getLastUpdate, + getTranslationOriginal, + isMicro, + isTranslation, +} from "@lib/collection/helpers"; import type { CollectionEntry } from "astro:content"; interface Props { - posts: (CollectionEntry<"blog"> & { data: z.infer<typeof Original> })[]; + posts: CollectionEntry<"blog">[]; + small?: boolean; + dateOptions?: Intl.DateTimeFormatOptions; } -const { posts } = Astro.props; +const { + posts, + small = false, + dateOptions = { year: "numeric", month: "long", day: "numeric" }, +} = Astro.props; --- -<ol> +<ol class:list={{ "remove-nums": !small }}> { - await Promise.all(posts.map(async (post) => { - const { id, data } = post; - const { title, description, lang, keywords } = data; - const { name, email, entity } = await getFirstUserID(post); - const display = name ?? email ?? entity; - return ( + await Promise.all( + posts.map( + async (post) => { + const { id, data } = post; + const { title, lang } = data; + const description = isMicro(post) + ? post.rendered?.html + : ("description" in data ? data.description : undefined); + const keywords = isTranslation(post) + ? await getTranslationOriginal(post).then((x) => + x?.data?.keywords + ) + : ("keywords" in data ? data.keywords : undefined); + const { name, email, entity } = await getFirstUserID(post); + const display = name ?? email ?? entity; + return ( <li> <article itemprop="blogPost" @@ -29,36 +49,39 @@ const { posts } = Astro.props; <h3> <a href={`/blog/read/${id}`} itemprop="headline name">{title}</a> </h3> - <div itemprop="abstract"> - { - description && - description.split("\n\n").map((paragraph) => ( - <p class="small">{paragraph}</p> - )) - } - </div> + { + description && ( + <div itemprop="abstract"> + { + isMicro(post) + ? <Fragment set:html={description} /> + : description.split("\n\n").map(( + paragraph, + ) => <p class:list={{ small }}>{paragraph}</p>) + } + </div> + ) + } <footer class="small"> <Date date={getLastUpdate(post)} locales={lang} - options={{ - year: "numeric", - month: "long", - day: "numeric", - }} + options={dateOptions} itemprop="dateModified" /><span itemprop="author" itemscope itemtype="https://schema.org/Person" ><span itemprop="alternateName">{display}</span></span> - <KeywordsList {keywords} /> + {Array.isArray(keywords) && <KeywordsList {keywords} />} </footer> </article> </li> ); - })) + }, + ), + ) } </ol> @@ -82,6 +105,20 @@ const { posts } = Astro.props; } } } + + & > li:not(:first-of-type) { + border-block-start: 1px solid var(--color-dark); + } + } + + ol.remove-nums { + margin-inline-start: 0; + & > li { + margin-inline-end: calc(var(--size-7) * 1em); + & > article { + padding-inline-end: 0; + } + } } @media (width >= 40rem) { |