mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 15:48:23 -05:00
Allow articles to have many authors with links
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const authorName = z.string().min(1);
|
||||
|
||||
const author = z.union([
|
||||
authorName,
|
||||
z.object({ name: authorName, link: z.string().url() }),
|
||||
]);
|
||||
|
||||
export const articleDataSchema = z.object({
|
||||
title: z.string().min(1),
|
||||
author: z.string().min(1),
|
||||
author: z.union([author, z.array(author)]),
|
||||
date: z.date(),
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import matter from "gray-matter";
|
||||
import { ZodError } from "zod";
|
||||
import { ZodError, type z } from "zod";
|
||||
import { ARTICLES_FOLDER_PATH } from "../articles-constants";
|
||||
import { articleDataSchema } from "../articles-schemas.server";
|
||||
|
||||
@@ -23,8 +23,8 @@ export function articleBySlug(slug: string) {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}),
|
||||
authorLink: authorToLink(restParsed.author),
|
||||
...restParsed,
|
||||
authors: normalizeAuthors(restParsed.author),
|
||||
title: restParsed.title,
|
||||
};
|
||||
} catch (e) {
|
||||
if (!(e instanceof Error)) throw e;
|
||||
@@ -37,8 +37,20 @@ export function articleBySlug(slug: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function authorToLink(author: string) {
|
||||
if (author === "Riczi") return "https://twitter.com/Riczi2k";
|
||||
export function normalizeAuthors(
|
||||
authors: z.infer<typeof articleDataSchema>["author"],
|
||||
): Array<{ name: string; link: string | null }> {
|
||||
if (Array.isArray(authors)) {
|
||||
return authors.map((author) => {
|
||||
if (typeof author === "string") {
|
||||
return { name: author, link: null };
|
||||
}
|
||||
return author;
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
if (typeof authors === "string") {
|
||||
return [{ name: authors, link: null }];
|
||||
}
|
||||
return [authors];
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import matter from "gray-matter";
|
||||
import type { z } from "zod";
|
||||
import { ARTICLES_FOLDER_PATH } from "../articles-constants";
|
||||
import { articleDataSchema } from "../articles-schemas.server";
|
||||
import { type articleBySlug, normalizeAuthors } from "./bySlug.server";
|
||||
|
||||
export async function mostRecentArticles(count: number) {
|
||||
const files = await fs.promises.readdir(ARTICLES_FOLDER_PATH);
|
||||
|
||||
const articles: Array<
|
||||
z.infer<typeof articleDataSchema> & {
|
||||
Omit<NonNullable<ReturnType<typeof articleBySlug>>, "content"> & {
|
||||
slug: string;
|
||||
dateString: string;
|
||||
}
|
||||
@@ -21,15 +21,17 @@ export async function mostRecentArticles(count: number) {
|
||||
);
|
||||
const { data } = matter(rawMarkdown);
|
||||
|
||||
const articleData = articleDataSchema.parse(data);
|
||||
const { date, ...restParsed } = articleDataSchema.parse(data);
|
||||
articles.push({
|
||||
...articleData,
|
||||
date,
|
||||
slug: file.replace(".md", ""),
|
||||
dateString: articleData.date.toLocaleDateString("en-US", {
|
||||
dateString: date.toLocaleDateString("en-US", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}),
|
||||
authors: normalizeAuthors(restParsed.author),
|
||||
title: restParsed.title,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
MetaFunction,
|
||||
SerializeFrom,
|
||||
} from "@remix-run/node";
|
||||
import { useLoaderData } from "@remix-run/react";
|
||||
import { Link, useLoaderData } from "@remix-run/react";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import * as React from "react";
|
||||
import { Main } from "~/components/Main";
|
||||
@@ -88,9 +88,22 @@ export default function ArticlePage() {
|
||||
function Author() {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
if (data.authorLink) {
|
||||
return <a href={data.authorLink}>{data.author}</a>;
|
||||
}
|
||||
return data.authors.map((author, i) => {
|
||||
if (!author.link) return author.name;
|
||||
|
||||
return <>{data.author}</>;
|
||||
const authorLink = author.link.includes("https://sendou.ink")
|
||||
? author.link.replace("https://sendou.ink", "")
|
||||
: author.link;
|
||||
|
||||
return (
|
||||
<React.Fragment key={author.name}>
|
||||
{author.link.includes("https://sendou.ink") ? (
|
||||
<Link to={authorLink}>{author.name}</Link>
|
||||
) : (
|
||||
<a href={author.link}>{author.name}</a>
|
||||
)}
|
||||
{i < data.authors.length - 1 ? " & " : ""}
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ARTICLES_MAIN_PAGE, articlePage, navIconUrl } from "~/utils/urls";
|
||||
import { mostRecentArticles } from "../core/list.server";
|
||||
|
||||
import "~/styles/front.css";
|
||||
import { joinListToNaturalString } from "../../../utils/arrays";
|
||||
|
||||
const MAX_ARTICLES_COUNT = 100;
|
||||
|
||||
@@ -39,8 +40,13 @@ export default function ArticlesMainPage() {
|
||||
{article.title}
|
||||
</Link>
|
||||
<div className="text-xs text-lighter">
|
||||
{t("common:articles.by", { author: article.author })} •{" "}
|
||||
<time>{article.dateString}</time>
|
||||
{t("common:articles.by", {
|
||||
author: joinListToNaturalString(
|
||||
article.authors.map((a) => a.name),
|
||||
"&",
|
||||
),
|
||||
})}{" "}
|
||||
• <time>{article.dateString}</time>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user