diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/organisms/KeywordsList.astro | 11 | ||||
-rw-r--r-- | src/components/organisms/RelativeTime.astro | 19 | ||||
-rw-r--r-- | src/components/templates/MicroBlog.astro | 66 | ||||
-rw-r--r-- | src/components/templates/PrevNextNav.astro | 13 | ||||
-rw-r--r-- | src/layouts/PrevNext.astro | 2 | ||||
-rw-r--r-- | src/pages/blog/[...date].astro | 6 | ||||
-rw-r--r-- | src/pages/blog/micro/[page].astro | 42 | ||||
-rw-r--r-- | src/pages/index.astro | 2 | ||||
-rw-r--r-- | src/utils/datetime.test.ts | 74 | ||||
-rw-r--r-- | src/utils/datetime.ts | 25 |
10 files changed, 228 insertions, 32 deletions
diff --git a/src/components/organisms/KeywordsList.astro b/src/components/organisms/KeywordsList.astro index d3b9e7f..44c5641 100644 --- a/src/components/organisms/KeywordsList.astro +++ b/src/components/organisms/KeywordsList.astro @@ -7,7 +7,13 @@ const { keywords } = Astro.props; --- <p> - {keywords.map((x) => <span>#<b itemprop="keywords">{x}</b></span>)} + { + keywords.map((x) => ( + <span><a href={`/blog/keywords/${x}`}>#<b itemprop="keywords">{ + x + }</b></a></span> + )) + } </p> <style> @@ -16,8 +22,9 @@ const { keywords } = Astro.props; flex-direction: row-reverse; flex-wrap: wrap; gap: calc(var(--size-0) * 1em); + margin-block: calc(var(--size-0) * 1em); - & > * { + & > * > * { border-radius: calc(infinity * 1px); background-color: color-mix( in srgb, diff --git a/src/components/organisms/RelativeTime.astro b/src/components/organisms/RelativeTime.astro new file mode 100644 index 0000000..13d531d --- /dev/null +++ b/src/components/organisms/RelativeTime.astro @@ -0,0 +1,19 @@ +--- +import type { HTMLAttributes } from "astro/types"; +import { getRelativeTimeUnit } from "@utils/datetime"; + +interface Props { + date: Date; + locales: Intl.LocalesArgument; + options: Intl.RelativeTimeFormatOptions; + itemprop: HTMLAttributes<"time">["itemprop"]; +} + +const { date, locales, options } = Astro.props; + +const format = new Intl.RelativeTimeFormat(locales, options).format( + ...getRelativeTimeUnit(date), +); +--- + +{format} diff --git a/src/components/templates/MicroBlog.astro b/src/components/templates/MicroBlog.astro index 88321dc..713e7eb 100644 --- a/src/components/templates/MicroBlog.astro +++ b/src/components/templates/MicroBlog.astro @@ -1,12 +1,17 @@ --- import Date from "@components/organisms/Date.astro"; +import RelativeTime from "@components/organisms/RelativeTime.astro"; import KeywordsList from "@components/organisms/KeywordsList.astro"; import { getFirstUserID, getLastUpdate } from "@lib/collection/helpers"; -import type { Micro, MicroEntry } from "@lib/collection/schemas"; +import type { MicroEntry } from "@lib/collection/schemas"; +import { getRelativeTimeUnit } from "@utils/datetime"; -interface Props extends MicroEntry {} +interface Props { + micro: MicroEntry; + seeAll?: boolean; +} -const micro = Astro.props; +const { micro, seeAll = false } = Astro.props; const { id, data, rendered } = micro; const { title, lang, keywords } = data; const date = getLastUpdate(micro); @@ -20,6 +25,7 @@ const little = ((first?.[0] ?? "") + (last?.[0] ?? "")).slice(0, 2); itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting" + style={`--rows: ${seeAll ? 3 : 2};`} > <header> <h3 class="title"> @@ -36,35 +42,49 @@ const little = ((first?.[0] ?? "") + (last?.[0] ?? "")).slice(0, 2); itemscope itemtype="https://schema.org/Person" ><span itemprop="alternateName">{first} {last}</span></span> - <span class="small">· <Date + <span class="small" + >· <Date {date} locales={lang} options={{ month: "short", day: "numeric" }} itemprop="dateModified" - /></span> + /> (<RelativeTime + {date} + locales={lang} + options={{ + style: "short", + numeric: "auto", + }} + />)</span> </div> + <span class="lang mute">{lang}</span> </header> <div class="content small"> <div {lang} itemprop="articleBody text"> <Fragment set:html={rendered?.html} /> </div> <footer> + <a href={`/blog/read/${id}`}>Ver mais...</a> <div class="keywords"><KeywordsList {keywords} /></div> </footer> </div> - <aside> - <small><a href="/blog/micro/1">Ver todos os microposts</a></small> - </aside> + { + seeAll && ( + <aside> + <small><a href="/blog/micro/1">Ver todos os microposts</a></small> + </aside> + ) + } </article> <style> article { - border-radius: calc(var(--size-1) * 1em); + --rows: 2 border-radius: calc(var(--size-1) * 1em); box-shadow: 0 0 calc(var(--size-1) * 1em) var(--color-light); padding: calc(var(--size-4) * 1em); display: grid; - grid-template-rows: repeat(3, auto); - grid-template-columns: calc(var(--size-9) * 1em) auto; + grid-template-rows: repeat(var(--rows), auto); + grid-template-columns: calc(var(--size-9) * 1em) repeat(2, auto); gap: calc(var(--size-1) * 1em); & > header { @@ -73,7 +93,7 @@ const little = ((first?.[0] ?? "") + (last?.[0] ?? "")).slice(0, 2); & > aside { grid-row: 3 / 4; - grid-column: 1 / 3; + grid-column: 1 / 4; border-block-start: 1px solid #e7e7e7; padding-block-start: calc(var(--size-1) * 1em); } @@ -93,7 +113,8 @@ const little = ((first?.[0] ?? "") + (last?.[0] ?? "")).slice(0, 2); color: #fff; font-weight: 950; font-size: smaller; - border-radius: calc(infinity * 1px); + /* border-radius: calc(infinity * 1px); */ + border-radius: calc(var(--size-3) * 1em); text-align: center; text-transform: uppercase; } @@ -102,15 +123,32 @@ const little = ((first?.[0] ?? "") + (last?.[0] ?? "")).slice(0, 2); .title { display: none; } + + .lang { + grid-row: 1/2; + grid-column: 3 / 4; + display: flex; + justify-content: flex-end; + align-items: baseline; + } + .content { grid-row: 2/3; - grid-column: 2 / 3; + grid-column: 2 / 4; & > [lang] > :global(*:first-child) { margin-block-start: 0; } & > [lang] > :global(*:last-child) { margin-block-end: 0; } + + & > footer { + display: flex; + gap: 1em; + flex-wrap: wrap; + justify-content: space-between; + align-items: baseline; + } } .keywords { diff --git a/src/components/templates/PrevNextNav.astro b/src/components/templates/PrevNextNav.astro index fe8bd8d..f60a4cd 100644 --- a/src/components/templates/PrevNextNav.astro +++ b/src/components/templates/PrevNextNav.astro @@ -1,21 +1,23 @@ --- interface Props { + first?: string | URL; previous?: string | URL; next?: string | URL; + last?: string | URL; label?: string; } -const { next, previous, label } = Astro.props; +const { first, next, previous, last, label } = Astro.props; --- { - (next || previous) && ( + (first || next || previous || last) && ( <nav> + {first && <p><a href={Astro.props.first}>Primeiro</a></p>} { previous && ( <p> - < <a rel="prev" href={`/blog/${Astro.props.previous}`} - >Anterior</a> + < <a rel="prev" href={Astro.props.previous}>Anterior</a> </p> ) } @@ -23,10 +25,11 @@ const { next, previous, label } = Astro.props; { next && ( <p> - <a rel="next" href={`/blog/${Astro.props.next}`}>Próximo</a> > + <a rel="next" href={Astro.props.next}>Próximo</a> > </p> ) } + {last && <p><a href={Astro.props.last}>Último</a></p>} </nav> ) } diff --git a/src/layouts/PrevNext.astro b/src/layouts/PrevNext.astro index 64db40b..58e3c2a 100644 --- a/src/layouts/PrevNext.astro +++ b/src/layouts/PrevNext.astro @@ -2,8 +2,10 @@ import PrevNextNav from "@components/templates/PrevNextNav.astro"; interface Props { + first?: string | URL; previous?: string | URL; next?: string | URL; + last?: string | URL; label?: string; } --- diff --git a/src/pages/blog/[...date].astro b/src/pages/blog/[...date].astro index a0764be..8346baf 100644 --- a/src/pages/blog/[...date].astro +++ b/src/pages/blog/[...date].astro @@ -211,7 +211,11 @@ const description = "Ultímas publicações" + > <h2 itemprop="name description" set:html={title} /> <DateSelector {date} {years} {months} {days} /> - <PrevNext {previous} {next} label={format}> + <PrevNext + previous={`/blog/${previous}`} + next={`/blog/${next}`} + label={format} + > <SimplePostList {posts} dateOptions={{ diff --git a/src/pages/blog/micro/[page].astro b/src/pages/blog/micro/[page].astro index 9fb04f1..72ccdcf 100644 --- a/src/pages/blog/micro/[page].astro +++ b/src/pages/blog/micro/[page].astro @@ -1,6 +1,7 @@ --- import MicroBlog from "@components/templates/MicroBlog.astro"; import Base from "@layouts/Base.astro"; +import PrevNext from "@layouts/PrevNext.astro"; import { fromPosts, isMicro } from "@lib/collection/helpers"; import { identity } from "@utils/anonymous"; import type { @@ -19,14 +20,39 @@ export type Params = InferGetStaticParamsType<typeof getStaticPaths>; export type Props = InferGetStaticPropsType<typeof getStaticPaths>; const { page } = Astro.props; + +const { prev, next, first, last } = page.url; --- <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} + <main + itemprop="mainContentOfPage" + itemscope + itemtype="https://schema.org/WebPageElement" + > + <section + id="posts" + itemprop="citation" + itemscope + itemtype="http://schema.org/Blog" + > + <h2 itemprop="name description">Página {page.currentPage}</h2> + <PrevNext previous={prev} {next} {first} {last}> + <ul>{page.data.map((micro) => <li><MicroBlog {micro} /></li>)}</ul> + </PrevNext> + </section> + </main> </Base> +<style> + ul { + max-width: 40ch; + margin-inline: auto; + margin-block: calc(var(--size-7) * 1em); + padding-inline: 0; + list-style-type: none; + & > li { + width: 100%; + margin-block: calc(var(--size-0) * 1em); + margin-inline: auto; + } + } +</style> diff --git a/src/pages/index.astro b/src/pages/index.astro index bb4108c..baadbc7 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -55,7 +55,7 @@ const micro = await fromPosts( itemtype="http://schema.org/Blog" > <h2 itemprop="name description">Últimas publicações atualizadas</h2> - {micro && <div id="last-micro"><MicroBlog {...micro} /></div>} + {micro && <div id="last-micro"><MicroBlog {micro} seeAll /></div>} <div id="last-originals"> <SimplePostList posts={originals} small /> </div> diff --git a/src/utils/datetime.test.ts b/src/utils/datetime.test.ts index 5f0749d..03e2771 100644 --- a/src/utils/datetime.test.ts +++ b/src/utils/datetime.test.ts @@ -1,6 +1,10 @@ import { assertEquals, assertMatch } from "@std/assert"; import { describe, it } from "@std/testing/bdd"; -import { toIso8601Full, toIso8601FullUTC } from "./datetime.ts"; +import { + getRelativeTimeUnit, + toIso8601Full, + toIso8601FullUTC, +} from "./datetime.ts"; describe("toIso8601Full", () => { it("formats current local time with offset", () => { @@ -60,3 +64,71 @@ describe("toIso8601FullUTC", () => { assertMatch(result, /^-\d{6}-03-15T12:00:00\.000Z$/); }); }); + +describe("getRelativeTimeUnit", () => { + const now = new Date(); + + it("returns seconds for differences under a minute", () => { + const future = new Date(now.getTime() + 45 * 1000); + const [value, unit] = getRelativeTimeUnit(future); + + assertEquals(unit, "second"); + assertEquals(value, 1); + }); + + it("returns minutes for differences under an hour but over a minute", () => { + const future = new Date(now.getTime() + 15 * 60 * 1000); + const [value, unit] = getRelativeTimeUnit(future); + + assertEquals(unit, "minute"); + assertEquals(value, 15); + }); + + it("returns hours for differences under a day but over an hour", () => { + const past = new Date(now.getTime() - 3.2 * 60 * 60 * 1000); + const [value, unit] = getRelativeTimeUnit(past); + + assertEquals(unit, "hour"); + assertEquals(value, -3); + }); + + it("returns days for differences under a week but over a day", () => { + const past = new Date(now.getTime() - 2.5 * 24 * 60 * 60 * 1000); + const [value, unit] = getRelativeTimeUnit(past); + + assertEquals(unit, "day"); + assertEquals(value, -3); + }); + + it("returns weeks for differences under a month but over a week", () => { + const future = new Date(now.getTime() + 2.3 * 7 * 24 * 60 * 60 * 1000); + const [value, unit] = getRelativeTimeUnit(future); + + assertEquals(unit, "week"); + assertEquals(value, 2); + }); + + it("returns months for differences under a quarter but over a month", () => { + const past = new Date(now.getTime() - 1.5 * 30 * 24 * 60 * 60 * 1000); + const [value, unit] = getRelativeTimeUnit(past); + + assertEquals(unit, "month"); + assertEquals(value, -2); + }); + + it("returns quarters for differences under a year but over a quarter", () => { + const future = new Date(now.getTime() + 5 * 30 * 24 * 60 * 60 * 1000); + const [value, unit] = getRelativeTimeUnit(future); + + assertEquals(unit, "quarter"); + assertEquals(value, 2); + }); + + it("returns years for differences over a year", () => { + const past = new Date(now.getTime() - 3.4 * 365 * 24 * 60 * 60 * 1000); + const [value, unit] = getRelativeTimeUnit(past); + + assertEquals(unit, "year"); + assertEquals(value, -3); + }); +}); diff --git a/src/utils/datetime.ts b/src/utils/datetime.ts index 3a2cd25..c32fde0 100644 --- a/src/utils/datetime.ts +++ b/src/utils/datetime.ts @@ -41,3 +41,28 @@ export function toIso8601FullUTC(date: Date): string { } const pad = (num: number, len = 2) => String(Math.abs(num)).padStart(len, "0"); + +export function getRelativeTimeUnit( + date: Date, + now: Date = new Date(), +): Parameters<Intl.RelativeTimeFormat["format"]> { + const diffMs = date.getTime() - now.getTime(); + + const seconds = diffMs / 1000; + const minutes = seconds / 60; + const hours = minutes / 60; + const days = hours / 24; + const weeks = days / 7; + const months = days / 30; + const quarters = months / 3; + const years = days / 365; + + if (Math.abs(years) >= 1) return [Math.round(years), "year"]; + if (Math.abs(quarters) >= 1) return [Math.round(quarters), "quarter"]; + if (Math.abs(months) >= 1) return [Math.round(months), "month"]; + if (Math.abs(weeks) >= 1) return [Math.round(weeks), "week"]; + if (Math.abs(days) >= 1) return [Math.round(days), "day"]; + if (Math.abs(hours) >= 1) return [Math.round(hours), "hour"]; + if (Math.abs(minutes) >= 1) return [Math.round(minutes), "minute"]; + return [Math.round(seconds), "second"]; +} |