feat(library): add multilingual author archives and linked bylines
This commit is contained in:
23 files changed
+2098
-115
No files matched your search
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import { getCollection, type CollectionEntry } from 'astro:content';
|
||||
import { createAuthorResolver } from '../utils/authors';
|
||||
import LikeViewCounter from './LikeViewCounter.astro';
|
||||
|
||||
interface Props {
|
||||
@@ -8,13 +9,14 @@ interface Props {
|
||||
}
|
||||
|
||||
const { post, prefix = '' } = Astro.props;
|
||||
const author = createAuthorResolver(await getCollection('authors'))(post.data.lang, post.data.author);
|
||||
const href = `${prefix}/library/${post.data.slug}`.replace('//', '/');
|
||||
const readMore = { en: 'Read More', es: 'Leer Más', ar: 'اقرأ المزيد' }[post.data.lang] || 'Read More';
|
||||
---
|
||||
<article class="card blog-card">
|
||||
{post.data.featuredImage && <a href={href}><img src={post.data.featuredImage} alt={post.data.alt || ''} loading="lazy" /></a>}
|
||||
<div class="card-body">
|
||||
<div class="blog-card-meta"><img src="/assets/images/rula-diab-avatar.jpg" alt="" /><p>Rula Diab, Clinical Director, M.Ed, BCBA, LBA<br /><time datetime={post.data.date.toISOString()}>{post.data.date.toLocaleDateString(post.data.lang, { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' })}</time></p></div>
|
||||
<div class="blog-card-meta">{author.avatar && <img src={author.avatar} alt="" loading="lazy" />}<p><a class="author-byline" rel="author" href={author.archiveRoute}><bdi>{author.displayName}</bdi></a><br /><time datetime={post.data.date.toISOString()}>{post.data.date.toLocaleDateString(post.data.lang, { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' })}</time></p></div>
|
||||
<h3><a href={href}>{post.data.title}</a></h3>
|
||||
<p>{post.data.description}</p>
|
||||
<div class="blog-card-footer" style="align-items:center;display:flex;justify-content:space-between;gap:var(--space-lg);margin-top:auto;">
|
||||
@@ -25,6 +27,9 @@ const readMore = { en: 'Read More', es: 'Leer Más', ar: 'اقرأ المزيد'
|
||||
</article>
|
||||
|
||||
<style>
|
||||
.author-byline { color: inherit; text-decoration: none; }
|
||||
.author-byline:hover { text-decoration: underline; }
|
||||
.author-byline:focus-visible { outline: 3px solid var(--color-accent); outline-offset: 3px; }
|
||||
.blog-card-meta { align-items: center; display: flex; font-size: .78rem; gap: .75rem; }
|
||||
.blog-card-meta img { aspect-ratio: 1; border-radius: 50%; height: 42px; width: 42px; }
|
||||
.blog-card { background: white; border: 0; border-radius: 4px; box-shadow: rgba(54, 48, 48, 0.28) 0 0 12px 0; display: flex; margin-bottom: 40px; overflow: hidden; }
|
||||
|
||||
@@ -4,13 +4,16 @@ import {
|
||||
type LibraryPageSlice
|
||||
} from '../utils/library-pagination';
|
||||
import type { LibraryLocale } from '../utils/library-catalog';
|
||||
import { authorPageRoute } from '../utils/author-archives';
|
||||
|
||||
interface Props {
|
||||
locale: LibraryLocale;
|
||||
page: LibraryPageSlice<unknown>;
|
||||
authorRoot?: string;
|
||||
}
|
||||
|
||||
const { locale, page } = Astro.props;
|
||||
const { locale, page, authorRoot } = Astro.props;
|
||||
const pageRoute = (number: number) => authorRoot ? authorPageRoute(authorRoot, number) : libraryPageRoute(locale, number);
|
||||
const messages = {
|
||||
en: { navigation: 'Library pagination', previous: 'Previous', next: 'Next', page: 'Page', current: 'current page' },
|
||||
es: { navigation: 'Paginación de la biblioteca', previous: 'Anterior', next: 'Siguiente', page: 'Página', current: 'página actual' },
|
||||
@@ -23,13 +26,13 @@ const pages = Array.from({ length: page.totalPages }, (_, index) => index + 1);
|
||||
<ul>
|
||||
{page.previousPage && (
|
||||
<li class="library-pagination__previous">
|
||||
<a rel="prev" href={libraryPageRoute(locale, page.previousPage)}>{messages.previous}</a>
|
||||
<a rel="prev" href={pageRoute(page.previousPage)}>{messages.previous}</a>
|
||||
</li>
|
||||
)}
|
||||
{pages.map((number) => (
|
||||
<li>
|
||||
<a
|
||||
href={libraryPageRoute(locale, number)}
|
||||
href={pageRoute(number)}
|
||||
aria-current={number === page.currentPage ? 'page' : undefined}
|
||||
aria-label={`${messages.page} ${number}${number === page.currentPage ? `, ${messages.current}` : ''}`}
|
||||
>{number}</a>
|
||||
@@ -37,7 +40,7 @@ const pages = Array.from({ length: page.totalPages }, (_, index) => index + 1);
|
||||
))}
|
||||
{page.nextPage && (
|
||||
<li class="library-pagination__next">
|
||||
<a rel="next" href={libraryPageRoute(locale, page.nextPage)}>{messages.next}</a>
|
||||
<a rel="next" href={pageRoute(page.nextPage)}>{messages.next}</a>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
import { render, type CollectionEntry } from 'astro:content';
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import PageHero from '../PageHero.astro';
|
||||
import BlogCard from '../BlogCard.astro';
|
||||
import LibraryPagination from '../LibraryPagination.astro';
|
||||
import { getLibraryCatalog } from '../../utils/library';
|
||||
import { libraryRootRoute, paginateLibraryItems } from '../../utils/library-pagination';
|
||||
import type { AuthorArchiveDescriptor } from '../../utils/author-archives';
|
||||
|
||||
interface Props { page: AuthorArchiveDescriptor<CollectionEntry<'authors'>>; }
|
||||
const { page } = Astro.props;
|
||||
const { author, locale, currentPage } = page;
|
||||
const catalog = (await getLibraryCatalog()).byLocale[locale];
|
||||
const slice = paginateLibraryItems(catalog.filter(({ entry }) => entry.data.author === author.slug), currentPage);
|
||||
if (!slice?.items.length) throw new Error(`Empty author archive: ${author.archiveRoute}, page ${currentPage}`);
|
||||
const { Content } = await render(author.entry);
|
||||
const prefix = locale === 'en' ? '' : `/${locale}`;
|
||||
const text = {
|
||||
en: { heading: 'The Library at AZ Institute for Autism', title: 'Library & News Room | Arizona Institute for Autism', page: 'Page' },
|
||||
es: { heading: 'La Biblioteca en AZ Institute for Autism', title: 'Biblioteca y Noticias | Instituto de Autismo de Arizona', page: 'Página' },
|
||||
ar: { heading: 'مكتبة معهد أريزونا للتوحد', title: 'المكتبة وغرفة الأخبار | معهد أريزونا للتوحد', page: 'الصفحة' }
|
||||
}[locale];
|
||||
const title = `${text.title} | ${author.displayName}${currentPage > 1 ? ` | ${text.page} ${currentPage}` : ''}`;
|
||||
// Use the existing catalog's latest-post treatment; no second article list.
|
||||
const popular = catalog.slice(0, 5);
|
||||
---
|
||||
<BaseLayout title={title} description={author.description} image={author.avatar} lang={locale}>
|
||||
<PageHero title={text.heading} image="/assets/images/hero-library-index.webp" eyebrow="Welcome to" constrain />
|
||||
<section class="author-profile" aria-labelledby="author-heading">
|
||||
{author.avatar && <img class="author-profile__avatar" src={author.avatar} alt={author.displayName} width="176" height="176" />}
|
||||
<div class="author-profile__content">
|
||||
<h2 id="author-heading"><bdi>{author.displayName}</bdi></h2>
|
||||
<div class="author-biography"><Content /></div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="author-listing container">
|
||||
<section aria-labelledby="author-posts-heading">
|
||||
<h2 id="author-posts-heading" class="author-posts-heading"><a href={libraryRootRoute(locale)}>All Post</a></h2>
|
||||
<div class="blog-list">{slice.items.map(({ entry }) => <BlogCard post={entry} prefix={prefix} />)}</div>
|
||||
<LibraryPagination locale={locale} page={slice} authorRoot={author.archiveRoute} />
|
||||
</section>
|
||||
<aside class="author-sidebar" aria-labelledby="author-popular-heading">
|
||||
<h2 id="author-popular-heading">Popular Posts</h2>
|
||||
<ul>{popular.map(({ entry, route }) => <li><a href={route}>{entry.data.title}</a></li>)}</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.author-profile { align-items: start; display: flex; gap: 4rem; margin: 100px auto 60px; max-width: 768px; width: calc(100% - 96px); }
|
||||
.author-profile__avatar { flex: 0 0 176px; height: 176px; object-fit: cover; width: 176px; }
|
||||
.author-profile__content { flex: 1; min-width: 0; }
|
||||
.author-profile h2 { font-size: 2.4rem; line-height: 1.25; margin: 0 0 10px; }
|
||||
.author-biography { font-size: 1rem; line-height: 26px; }
|
||||
.author-biography :global(p) { margin: 0 0 20px; }
|
||||
.author-listing { align-items: start; display: grid; gap: 30px; grid-template-columns: minmax(0, 1fr) 294px; padding-bottom: 60px; }
|
||||
.author-posts-heading { font-family: var(--font-body); font-size: 1rem; margin: 0 0 24px; }
|
||||
.author-posts-heading a { color: inherit; }
|
||||
.author-sidebar h2 { font-size: 1.25rem; margin: 0 0 20px; }
|
||||
.author-sidebar ul { list-style: none; margin: 0; padding: 0; }
|
||||
.author-sidebar li { margin-bottom: 20px; }
|
||||
.author-sidebar a { color: inherit; }
|
||||
.author-listing a:focus-visible { outline: 3px solid var(--color-accent); outline-offset: 3px; }
|
||||
@media (max-width: 760px) {
|
||||
.author-profile { flex-direction: column; gap: 5px; }
|
||||
.author-profile__avatar { flex-basis: auto; width: 100px; height: 100px; }
|
||||
.author-profile h2 { font-size: 2rem; }
|
||||
.author-biography { line-height: 20px; }
|
||||
.author-listing { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user