Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apps/web/content-collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const articles = defineCollection({
display_title: z.string().optional(),
meta_title: z.string().optional(),
meta_description: z.string().optional(),
author: z.string(),
author: z.union([z.string(), z.array(z.string())]),
date: z.string(),
coverImage: z.string().optional(),
featured: z.boolean().optional(),
Expand Down Expand Up @@ -122,7 +122,8 @@ const articles = defineCollection({

const slug = document._meta.path.replace(/\.mdx$/, "");

const author = document.author || "Char Team";
const rawAuthor = document.author || "Char Team";
const author = Array.isArray(rawAuthor) ? rawAuthor : [rawAuthor];
const title = document.display_title || document.meta_title;

return {
Expand Down
3 changes: 2 additions & 1 deletion apps/web/netlify/edge-functions/og.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,8 @@ function getAuthorAvatar(author: string): string {
}

function renderBlogTemplate(params: z.infer<typeof blogSchema>) {
const avatarUrl = getAuthorAvatar(params.author);
const firstAuthor = params.author.split(",")[0]?.trim() || params.author;
const avatarUrl = getAuthorAvatar(firstAuthor);

return (
<div
Expand Down
9 changes: 5 additions & 4 deletions apps/web/src/functions/github-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ function getDefaultFrontmatter(folder: string): string {
meta_title: ""
display_title: ""
meta_description: ""
author: "John Jeong"
author:
- "John Jeong"
featured: false
category: "Case Study"
date: "${today}"
Expand Down Expand Up @@ -1299,7 +1300,7 @@ export async function savePublishedArticleToBranch(
_metadata: {
meta_title?: string;
display_title?: string;
author?: string;
author?: string | string[];
},
): Promise<{
success: boolean;
Expand Down Expand Up @@ -1417,7 +1418,7 @@ export async function publishArticle(
branchName: string,
metadata: {
meta_title?: string;
author?: string;
author?: string | string[];
date?: string;
category?: string;
},
Expand All @@ -1435,7 +1436,7 @@ export async function publishArticle(
const body = `## Article ${statusText}

**Title:** ${metadata.meta_title || "Untitled"}
**Author:** ${metadata.author || "Unknown"}
**Author:** ${Array.isArray(metadata.author) ? metadata.author.join(", ") : metadata.author || "Unknown"}
**Date:** ${metadata.date || "Not set"}
**Category:** ${metadata.category || "Uncategorized"}

Expand Down
45 changes: 29 additions & 16 deletions apps/web/src/routes/_view/blog/$slug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,16 @@ export const Route = createFileRoute("/_view/blog/$slug")({
const relatedArticles = allArticles
.filter((a) => a.slug !== article.slug)
.sort((a, b) => {
const aScore = a.author === article.author ? 1 : 0;
const bScore = b.author === article.author ? 1 : 0;
const aScore = a.author.some((name: string) =>
article.author.includes(name),
)
? 1
: 0;
const bScore = b.author.some((name: string) =>
article.author.includes(name),
)
? 1
: 0;
if (aScore !== bScore) {
return bScore - aScore;
}
Expand All @@ -49,7 +57,7 @@ export const Route = createFileRoute("/_view/blog/$slug")({
const metaDescription = article.meta_description ?? "";
const ogImage =
article.coverImage ||
`https://hyprnote.com/og?type=blog&title=${encodeURIComponent(title)}${article.author ? `&author=${encodeURIComponent(article.author)}` : ""}${article.date ? `&date=${encodeURIComponent(new Date(article.date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }))}` : ""}&v=1`;
`https://hyprnote.com/og?type=blog&title=${encodeURIComponent(title)}${article.author.length > 0 ? `&author=${encodeURIComponent(article.author.join(", "))}` : ""}${article.date ? `&date=${encodeURIComponent(new Date(article.date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }))}` : ""}&v=1`;

return {
meta: [
Expand Down Expand Up @@ -77,8 +85,8 @@ export const Route = createFileRoute("/_view/blog/$slug")({
content: metaDescription,
},
{ name: "twitter:image", content: ogImage },
...(article.author
? [{ name: "author", content: article.author }]
...(article.author.length > 0
? [{ name: "author", content: article.author.join(", ") }]
: []),
{
property: "article:published_time",
Expand Down Expand Up @@ -114,8 +122,6 @@ function Component() {
}

function HeroSection({ article }: { article: any }) {
const avatarUrl = AUTHOR_AVATARS[article.author];

return (
<header className="py-12 lg:py-16 text-center px-4">
<Link
Expand All @@ -136,16 +142,23 @@ function HeroSection({ article }: { article: any }) {
{article.title}
</h1>

{article.author && (
{article.author.length > 0 && (
<div className="flex items-center justify-center gap-3 mb-2">
{avatarUrl && (
<img
src={avatarUrl}
alt={article.author}
className="w-8 h-8 rounded-full object-cover"
/>
)}
<p className="text-base text-neutral-600">{article.author}</p>
{article.author.map((name: string) => {
const avatarUrl = AUTHOR_AVATARS[name];
return (
<div key={name} className="flex items-center gap-2">
{avatarUrl && (
<img
src={avatarUrl}
alt={name}
className="w-8 h-8 rounded-full object-cover"
/>
)}
<p className="text-base text-neutral-600">{name}</p>
</div>
);
})}
</div>
)}

Expand Down
55 changes: 45 additions & 10 deletions apps/web/src/routes/_view/blog/index.tsx
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Array rendered directly as text in mobile view of ArticleListItem

In the mobile view (sm:hidden) of ArticleListItem, article.author (now a string[]) is rendered directly inside a <span> without .join(). React renders arrays by concatenating elements without separators, so ["John Jeong", "Yujong Lee"] would display as John JeongYujong Lee instead of John Jeong, Yujong Lee.

Root Cause

At apps/web/src/routes/_view/blog/index.tsx:577, the code reads:

<span className="text-sm text-neutral-500">{article.author}</span>

This was not updated to use .join(", ") like the other occurrences in the same file (e.g., line 559 and line 417). The desktop view at line 559 correctly uses Array.isArray(article.author) ? article.author.join(", ") : article.author, but this mobile counterpart was missed.

Impact: Multi-author articles display author names concatenated without any separator in the mobile view of the blog listing page.

(Refers to line 577)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,10 @@ function MostRecentFeaturedCard({ article }: { article: Article }) {
const [coverImageLoaded, setCoverImageLoaded] = useState(false);
const hasCoverImage = !coverImageError;
const displayDate = article.date;
const avatarUrl = AUTHOR_AVATARS[article.author];
const avatarUrl =
Array.isArray(article.author) && article.author.length > 0
? AUTHOR_AVATARS[article.author[0]]
: undefined;

return (
<Link
Expand Down Expand Up @@ -410,11 +413,19 @@ function MostRecentFeaturedCard({ article }: { article: Article }) {
{avatarUrl && (
<img
src={avatarUrl}
alt={article.author}
alt={
Array.isArray(article.author)
? article.author.join(", ")
: article.author
}
className="w-6 h-6 rounded-full object-cover"
/>
)}
<span>{article.author}</span>
<span>
{Array.isArray(article.author)
? article.author.join(", ")
: article.author}
</span>
<span>·</span>
<time dateTime={displayDate}>
{new Date(displayDate).toLocaleDateString("en-US", {
Expand All @@ -441,7 +452,10 @@ function OtherFeaturedCard({
const [coverImageLoaded, setCoverImageLoaded] = useState(false);
const hasCoverImage = !coverImageError;
const displayDate = article.date;
const avatarUrl = AUTHOR_AVATARS[article.author];
const avatarUrl =
Array.isArray(article.author) && article.author.length > 0
? AUTHOR_AVATARS[article.author[0]]
: undefined;

return (
<Link
Expand Down Expand Up @@ -507,11 +521,19 @@ function OtherFeaturedCard({
{avatarUrl && (
<img
src={avatarUrl}
alt={article.author}
alt={
Array.isArray(article.author)
? article.author.join(", ")
: article.author
}
className="w-5 h-5 rounded-full object-cover"
/>
)}
<span className="truncate">{article.author}</span>
<span className="truncate">
{Array.isArray(article.author)
? article.author.join(", ")
: article.author}
</span>
<span>·</span>
<time dateTime={displayDate} className="shrink-0">
{new Date(displayDate).toLocaleDateString("en-US", {
Expand All @@ -528,7 +550,10 @@ function OtherFeaturedCard({

function ArticleListItem({ article }: { article: Article }) {
const displayDate = article.date;
const avatarUrl = AUTHOR_AVATARS[article.author];
const avatarUrl =
Array.isArray(article.author) && article.author.length > 0
? AUTHOR_AVATARS[article.author[0]]
: undefined;

return (
<Link
Expand All @@ -551,12 +576,18 @@ function ArticleListItem({ article }: { article: Article }) {
{avatarUrl && (
<img
src={avatarUrl}
alt={article.author}
alt={
Array.isArray(article.author)
? article.author.join(", ")
: article.author
}
className="w-5 h-5 rounded-full object-cover"
/>
)}
<span className="text-sm text-neutral-500 whitespace-nowrap">
{article.author}
{Array.isArray(article.author)
? article.author.join(", ")
: article.author}
</span>
</div>
</div>
Expand All @@ -570,7 +601,11 @@ function ArticleListItem({ article }: { article: Article }) {
{avatarUrl && (
<img
src={avatarUrl}
alt={article.author}
alt={
Array.isArray(article.author)
? article.author.join(", ")
: article.author
}
className="w-5 h-5 rounded-full object-cover"
/>
)}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/routes/_view/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1736,7 +1736,7 @@ function BlogSection() {
{sortedArticles.map((article) => {
const ogImage =
article.coverImage ||
`https://hyprnote.com/og?type=blog&title=${encodeURIComponent(article.title ?? "")}${article.author ? `&author=${encodeURIComponent(article.author)}` : ""}${article.date ? `&date=${encodeURIComponent(new Date(article.date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }))}` : ""}&v=1`;
`https://hyprnote.com/og?type=blog&title=${encodeURIComponent(article.title ?? "")}${article.author.length > 0 ? `&author=${encodeURIComponent(article.author.join(", "))}` : ""}${article.date ? `&date=${encodeURIComponent(new Date(article.date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }))}` : ""}&v=1`;

return (
<Link
Expand Down
Loading