Skip to content

Commit be431fd

Browse files
authored
Merge pull request #2669 from appwrite/refactor-blog-multi-author-support
2 parents 9f4f9b9 + b298995 commit be431fd

File tree

7 files changed

+192
-148
lines changed

7 files changed

+192
-148
lines changed

src/lib/components/Article.svelte

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,64 @@
22
import Media from '$lib/UI/Media.svelte';
33
import { formatDate } from '$lib/utils/date';
44
5+
import type { AuthorInfo } from '$lib/utils/blog-authors';
6+
57
interface ArticleProps {
68
title: string;
79
cover: string;
810
href: string;
911
date: Date;
1012
timeToRead: number;
11-
author: string;
12-
avatar: string;
13+
authors: AuthorInfo[];
14+
avatars: string[];
1315
}
1416
15-
const { title, cover, href, date, timeToRead, author, avatar }: ArticleProps = $props();
17+
const { title, cover, href, date, timeToRead, authors, avatars }: ArticleProps = $props();
18+
19+
const authorAvatarPairs = $derived(
20+
avatars.map((avatar, i) => ({ avatar, author: authors[i] })).filter(({ avatar }) => avatar)
21+
);
1622
</script>
1723

1824
<li>
19-
<a class="bg-transparent" {href}>
20-
<div class="overflow-hidden rounded-lg">
21-
<Media
22-
src={cover}
23-
class="aspect-video transition-transform duration-250 hover:scale-105"
24-
alt={title}
25-
autoplay
26-
controls={false}
27-
/>
25+
<a class="block overflow-hidden rounded-lg bg-transparent" {href}>
26+
<Media
27+
src={cover}
28+
class="aspect-video transition-transform duration-250 hover:scale-105"
29+
alt={title}
30+
autoplay
31+
controls={false}
32+
/>
33+
</a>
34+
<div class="flex flex-col gap-3 pt-6 pb-3">
35+
<div class="text-caption text-secondary">
36+
{formatDate(date)} - {timeToRead} min
2837
</div>
29-
<div class="flex flex-col gap-3 pt-6 pb-3">
30-
<h4 class="text-label font-aeonik-pro text-primary line-clamp-2">
38+
<a {href} class="bg-transparent">
39+
<h4 class="text-label font-aeonik-pro text-primary line-clamp-2 hover:underline">
3140
{title}
3241
</h4>
33-
<div class="flex w-full">
34-
<div
35-
class="text-paragraph-md flex w-full flex-col items-center xl:flex-row xl:gap-2"
36-
>
37-
<div class="flex items-center justify-center gap-2">
38-
<img class="size-5 rounded-full" loading="lazy" src={avatar} alt={author} />
39-
<h4 class="text-primary">{author}</h4>
40-
</div>
41-
<div class="text-secondary ml-7 flex xl:ml-0">
42-
<span>
43-
{formatDate(date)}
44-
</span>
45-
<span class="before:mx-1 xl:before:content-['-']">{timeToRead} min</span>
46-
</div>
47-
</div>
42+
</a>
43+
<div class="text-paragraph-md flex flex-wrap items-center gap-2">
44+
<div class="flex items-center">
45+
{#each authorAvatarPairs as { avatar, author }, i}
46+
<img
47+
class="size-5 rounded-full ring-2 ring-[#19191c]"
48+
style="margin-inline-start: {i > 0
49+
? '-4px'
50+
: '0'}; z-index: {authorAvatarPairs.length - i}"
51+
loading="lazy"
52+
src={avatar}
53+
alt={author?.name ?? ''}
54+
/>
55+
{/each}
4856
</div>
57+
<span class="text-primary">
58+
{#each authors as author, i}
59+
<a href={author.href} class="hover:underline">{author.name}</a
60+
>{#if i < authors.length - 1},{' '}{/if}
61+
{/each}
62+
</span>
4963
</div>
50-
</a>
64+
</div>
5165
</li>
Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,67 @@
11
<script lang="ts">
22
import Media from '$lib/UI/Media.svelte';
33
import { formatDate } from '$lib/utils/date';
4+
import type { AuthorInfo } from '$lib/utils/blog-authors';
45
56
interface Props {
67
title: string;
78
cover: string;
89
href: string;
910
date: Date;
1011
timeToRead: number;
11-
author: string;
12-
avatar: string;
12+
authors: AuthorInfo[];
13+
avatars: string[];
1314
}
1415
15-
const { title, cover, href, date, timeToRead, author, avatar }: Props = $props();
16+
const { title, cover, href, date, timeToRead, authors, avatars }: Props = $props();
17+
18+
const authorAvatarPairs = $derived(
19+
avatars.map((avatar, i) => ({ avatar, author: authors[i] })).filter(({ avatar }) => avatar)
20+
);
1621
</script>
1722

18-
<a class="group flex w-full flex-col gap-8 bg-transparent pb-3 transition" {href}>
19-
<div class="overflow-hidden rounded-lg">
23+
<div class="group flex w-full flex-col gap-8 pb-3 transition">
24+
<a class="block overflow-hidden rounded-lg bg-transparent" {href}>
2025
<Media
2126
src={cover}
2227
class="aspect-video transition duration-250 ease-in-out group-hover:scale-105"
2328
alt={title}
2429
/>
25-
</div>
30+
</a>
2631
<div class="flex flex-col gap-5">
27-
<h4 class="text-label text-primary line-clamp-2">
28-
{title}
29-
</h4>
32+
<div class="text-caption text-secondary">
33+
{formatDate(date)} - {timeToRead} min
34+
</div>
35+
<a {href} class="bg-transparent">
36+
<h4 class="text-label text-primary line-clamp-2 hover:underline">
37+
{title}
38+
</h4>
39+
</a>
3040

3141
<div class="flex items-center gap-2">
32-
<img
33-
class="size-6 rounded-full"
34-
loading="lazy"
35-
src={avatar}
36-
width="24"
37-
height="24"
38-
alt={author}
39-
/>
42+
<div class="flex items-center">
43+
{#each authorAvatarPairs as { avatar, author }, i}
44+
<img
45+
class="size-6 rounded-full ring-2 ring-[#19191c]"
46+
style="margin-inline-start: {i > 0
47+
? '-8px'
48+
: '0'}; z-index: {authorAvatarPairs.length - i}"
49+
loading="lazy"
50+
src={avatar}
51+
width="24"
52+
height="24"
53+
alt={author?.name ?? ''}
54+
/>
55+
{/each}
56+
</div>
4057
<div class="flex items-baseline gap-3">
41-
<h4 class="text-sub-body text-primary">{author}</h4>
42-
<ul class="text-caption flex items-center gap-2">
43-
<li>
44-
{formatDate(date)}
45-
</li>
46-
<li>{timeToRead} min</li>
47-
</ul>
58+
<h4 class="text-sub-body text-primary">
59+
{#each authors as author, i}
60+
<a href={author.href} class="web-link">{author.name}</a
61+
>{#if i < authors.length - 1},{' '}{/if}
62+
{/each}
63+
</h4>
4864
</div>
4965
</div>
5066
</div>
51-
</a>
67+
</div>

src/lib/utils/blog-authors.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type { AuthorData } from '$routes/blog/content';
2+
3+
export interface AuthorInfo {
4+
name: string;
5+
href: string;
6+
}
7+
8+
/**
9+
* Extracts author information from a post for use in Article components
10+
*/
11+
export function getPostAuthors(
12+
postAuthor: string | string[],
13+
allAuthors: AuthorData[]
14+
): {
15+
postAuthors: AuthorInfo[];
16+
authorAvatars: string[];
17+
primaryAuthor: AuthorData | undefined;
18+
} {
19+
const postAuthorSlugs = Array.isArray(postAuthor) ? postAuthor : [postAuthor];
20+
if (postAuthorSlugs.length === 0) {
21+
return {
22+
postAuthors: [],
23+
authorAvatars: [],
24+
primaryAuthor: undefined
25+
};
26+
}
27+
28+
const authorsBySlug = new Map<string, AuthorData>();
29+
for (const author of allAuthors) {
30+
authorsBySlug.set(author.slug, author);
31+
}
32+
33+
const postAuthors: AuthorInfo[] = [];
34+
const authorAvatars: string[] = [];
35+
let primaryAuthor: AuthorData | undefined;
36+
37+
for (const slug of postAuthorSlugs) {
38+
const author = authorsBySlug.get(slug);
39+
if (author) {
40+
postAuthors.push({ name: author.name, href: author.href });
41+
authorAvatars.push(author.avatar || '');
42+
if (!primaryAuthor) {
43+
primaryAuthor = author;
44+
}
45+
}
46+
}
47+
48+
return {
49+
postAuthors,
50+
authorAvatars,
51+
primaryAuthor
52+
};
53+
}

src/markdoc/layouts/Author.svelte

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { TITLE_SUFFIX } from '$routes/titles';
77
import type { PostsData, AuthorData } from '$routes/blog/content';
88
import { DEFAULT_HOST } from '$lib/utils/metadata';
9+
import { getPostAuthors } from '$lib/utils/blog-authors';
910
import FloatingHead from '$lib/components/FloatingHead.svelte';
1011
1112
export let name: string;
@@ -170,23 +171,25 @@
170171
<div class="mt-12">
171172
<ul class="web-grid-articles">
172173
{#each posts.filter( (p) => (Array.isArray(p.author) ? p.author.includes(author?.slug ?? '') : p.author === author?.slug) ) as post}
173-
{@const postAuthorSlugs = Array.isArray(post.author)
174-
? post.author
175-
: [post.author]}
176-
{@const primaryAuthor =
177-
authors.find((a) => postAuthorSlugs.includes(a.slug)) ?? author}
178-
{@const authorNames = postAuthorSlugs
179-
.map((slug) => authors.find((a) => a.slug === slug)?.name)
180-
.filter(Boolean)
181-
.join(', ')}
174+
{@const authorData = getPostAuthors(post.author, authors)}
175+
{@const primaryAuthor = authorData.primaryAuthor ?? author}
182176
<Article
183177
title={post.title}
184178
href={post.href}
185179
cover={post.cover}
186180
date={post.date}
187181
timeToRead={post.timeToRead}
188-
avatar={primaryAuthor?.avatar ?? avatar}
189-
author={authorNames || primaryAuthor?.name || name}
182+
avatars={authorData.authorAvatars.length > 0
183+
? authorData.authorAvatars
184+
: [primaryAuthor?.avatar ?? avatar]}
185+
authors={authorData.postAuthors.length > 0
186+
? authorData.postAuthors
187+
: [
188+
{
189+
name: primaryAuthor?.name || name,
190+
href: primaryAuthor?.href || ''
191+
}
192+
]}
190193
/>
191194
{/each}
192195
</ul>

src/markdoc/layouts/Category.svelte

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { Article, FooterNav, MainFooter } from '$lib/components';
44
import { Main } from '$lib/layouts';
55
import { DEFAULT_HOST } from '$lib/utils/metadata';
6+
import { getPostAuthors } from '$lib/utils/blog-authors';
67
import type { AuthorData, PostsData } from '$routes/blog/content';
78
import { TITLE_SUFFIX } from '$routes/titles';
89
import { getContext } from 'svelte';
@@ -58,29 +59,19 @@
5859
<div class="mt-12">
5960
<ul class="web-grid-articles">
6061
{#each posts as post}
61-
{@const postAuthorSlugs = Array.isArray(post.author)
62-
? post.author
63-
: [post.author]}
64-
{@const primarySlug = postAuthorSlugs[0]}
65-
{@const author =
66-
authors.find((a) => a.slug === primarySlug) ||
67-
authors.find((a) => postAuthorSlugs.includes(a.slug))}
68-
{#if author}
69-
{@const authorNames = postAuthorSlugs
70-
.map((slug) => authors.find((a) => a.slug === slug)?.name)
71-
.filter(Boolean)}
72-
{@const authorLabel =
73-
authorNames.length > 1
74-
? `${authorNames[0]} +${authorNames.length - 1}`
75-
: authorNames[0] || author.name}
62+
{@const { postAuthors, authorAvatars, primaryAuthor } = getPostAuthors(
63+
post.author,
64+
authors
65+
)}
66+
{#if primaryAuthor}
7667
<Article
7768
title={post.title}
7869
href={post.href}
7970
cover={post.cover}
8071
date={post.date}
8172
timeToRead={post.timeToRead}
82-
avatar={author.avatar}
83-
author={authorLabel}
73+
avatars={authorAvatars}
74+
authors={postAuthors}
8475
/>
8576
{/if}
8677
{/each}

src/markdoc/layouts/Post.svelte

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
} from '$lib/utils/metadata';
2121
import { isAnnouncement, parseCategories } from '$lib/utils/blog-cta';
2222
import { prepareBlogCtaState, type BlogCallToActionInput } from '$lib/utils/blog-mid-cta';
23+
import { getPostAuthors } from '$lib/utils/blog-authors';
2324
import type { AuthorData, PostsData } from '$routes/blog/content';
2425
import { TITLE_SUFFIX } from '$routes/titles';
2526
import { getContext, setContext } from 'svelte';
@@ -191,29 +192,19 @@
191192
<section class="mt-8">
192193
<div class="grid grid-cols-1 gap-12 md:grid-cols-3">
193194
{#each posts.filter((p) => p.title !== title).slice(0, 3) as post}
194-
{@const postAuthorSlugs = Array.isArray(post.author)
195-
? post.author
196-
: [post.author]}
197-
{@const primarySlug = postAuthorSlugs[0]}
198-
{@const postAuthor =
199-
authors.find((a) => a.slug === primarySlug) ||
200-
authors.find((a) => postAuthorSlugs.includes(a.slug))}
201-
{#if postAuthor}
202-
{@const authorNames = postAuthorSlugs
203-
.map((slug) => authors.find((a) => a.slug === slug)?.name)
204-
.filter(Boolean)}
205-
{@const authorLabel =
206-
authorNames.length > 1
207-
? `${authorNames[0]} +${authorNames.length - 1}`
208-
: authorNames[0] || postAuthor.name}
195+
{@const { postAuthors, authorAvatars, primaryAuthor } = getPostAuthors(
196+
post.author,
197+
authors
198+
)}
199+
{#if primaryAuthor}
209200
<Article
210201
title={post.title}
211202
href={post.href}
212203
cover={post.cover}
213204
date={post.date}
214205
timeToRead={post.timeToRead}
215-
avatar={postAuthor.avatar}
216-
author={authorLabel}
206+
avatars={authorAvatars}
207+
authors={postAuthors}
217208
/>
218209
{/if}
219210
{/each}

0 commit comments

Comments
 (0)