mirror of
https://github.com/privacy-scaling-explorations/pse.dev.git
synced 2026-01-09 14:18:02 -05:00
feat: blog tags (#334)
* feat: blog tags * feat: fit limit passed to function
This commit is contained in:
12
Dockerfile
12
Dockerfile
@@ -3,11 +3,17 @@ RUN apk add --no-cache git curl
|
||||
|
||||
WORKDIR /builder
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
RUN corepack enable
|
||||
RUN yarn install
|
||||
# Copy manifests, lock file, and yarn config
|
||||
COPY package.json yarn.lock .yarnrc.yml ./
|
||||
|
||||
# Enable corepack and install dependencies using the lockfile
|
||||
RUN corepack enable
|
||||
RUN yarn install --immutable
|
||||
|
||||
# Copy the rest of the application code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN yarn build
|
||||
|
||||
# Create image by copying build artifacts
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
@@ -22,20 +21,13 @@ export default async function AboutPage({ params: { lang } }: any) {
|
||||
}) as any[]) ?? []
|
||||
|
||||
return (
|
||||
<Divider.Section className="bg-white">
|
||||
<PageHeader
|
||||
title={t("title")}
|
||||
subtitle={t("description")}
|
||||
image={
|
||||
<Image
|
||||
width={280}
|
||||
height={280}
|
||||
className="mx-auto h-[210px] w-[210px] lg:ml-auto lg:h-[320px] lg:w-[320px]"
|
||||
src="/logos/pse-logo-bg.svg"
|
||||
alt="pse logo"
|
||||
/>
|
||||
}
|
||||
actions={
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full bg-page-header-gradient">
|
||||
<AppContent className="flex flex-col gap-4 py-10 w-full max-w-[978px] mx-auto">
|
||||
<Label.PageTitle label={t("title")} />
|
||||
<h6 className="font-sans text-base font-normal text-tuatara-950 md:text-[18px] md:leading-[27px] md:max-w-[700px]">
|
||||
{t("description")}
|
||||
</h6>
|
||||
<Link
|
||||
href={siteConfig.links.discord}
|
||||
target="_blank"
|
||||
@@ -53,56 +45,57 @@ export default async function AboutPage({ params: { lang } }: any) {
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<AppContent className="container flex w-full max-w-[978px] flex-col gap-8 py-10 md:py-16">
|
||||
<Label.Section
|
||||
className="text-center"
|
||||
label={t("our-principles-title")}
|
||||
/>
|
||||
<Accordion
|
||||
type="multiple"
|
||||
items={[
|
||||
...principles.map((principle: any, index: number) => {
|
||||
return {
|
||||
label: principle.title,
|
||||
value: index.toString(),
|
||||
children: (
|
||||
<span className="flex flex-col gap-6 break-words pb-12 font-sans text-lg font-normal leading-[150%]">
|
||||
{principle.description.map(
|
||||
(description: string, index: number) => {
|
||||
return <p key={index}>{description}</p>
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
}
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
</AppContent>
|
||||
</div>
|
||||
<Divider.Section className="bg-white">
|
||||
<div className="flex justify-center">
|
||||
<AppContent className="container flex w-full max-w-[978px] flex-col gap-8 py-10 md:py-16">
|
||||
<Label.Section
|
||||
className="text-center"
|
||||
label={t("our-principles-title")}
|
||||
/>
|
||||
<Accordion
|
||||
type="multiple"
|
||||
items={[
|
||||
...principles.map((principle: any, index: number) => {
|
||||
return {
|
||||
label: principle.title,
|
||||
value: index.toString(),
|
||||
children: (
|
||||
<span className="flex flex-col gap-6 break-words pb-12 font-sans text-lg font-normal leading-[150%]">
|
||||
{principle.description.map(
|
||||
(description: string, index: number) => {
|
||||
return <p key={index}>{description}</p>
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
}
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
</AppContent>
|
||||
</div>
|
||||
|
||||
<Banner title={t("banner.title")} subtitle={t("banner.subtitle")}>
|
||||
<Link
|
||||
href={siteConfig.links.discord}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
passHref
|
||||
>
|
||||
<Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icons.discord fill="white" className="h-4" />
|
||||
<span className="text-[14px] uppercase">
|
||||
{common("joinOurDiscord")}
|
||||
</span>
|
||||
<Icons.externalUrl fill="white" className="h-5" />
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</Banner>
|
||||
</Divider.Section>
|
||||
<Banner title={t("banner.title")} subtitle={t("banner.subtitle")}>
|
||||
<Link
|
||||
href={siteConfig.links.discord}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
passHref
|
||||
>
|
||||
<Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icons.discord fill="white" className="h-4" />
|
||||
<span className="text-[14px] uppercase">
|
||||
{common("joinOurDiscord")}
|
||||
</span>
|
||||
<Icons.externalUrl fill="white" className="h-5" />
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</Banner>
|
||||
</Divider.Section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { blogArticleCardTagCardVariants } from "@/components/blog/blog-article-card"
|
||||
import { BlogContent } from "@/components/blog/blog-content"
|
||||
import { AppContent } from "@/components/ui/app-content"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Markdown } from "@/components/ui/markdown"
|
||||
import { getArticles, getArticleById } from "@/lib/blog"
|
||||
import { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
export const generateStaticParams = async () => {
|
||||
const articles = await getArticles()
|
||||
@@ -81,6 +83,18 @@ export default function BlogArticle({ params }: any) {
|
||||
{post?.tldr && <Markdown>{post?.tldr}</Markdown>}
|
||||
</div>
|
||||
) : null}
|
||||
{(post?.tags ?? [])?.length > 0 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-sm italic text-tuatara-950">Tags:</span>
|
||||
<div className="flex gap-2">
|
||||
{post?.tags?.map((tag) => (
|
||||
<Link key={tag} href={`/${params.lang}/blog?tag=${tag}`}>
|
||||
<Button size="xs">{tag}</Button>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</AppContent>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useTranslation } from "@/app/i18n"
|
||||
import { BlogArticles } from "@/components/blog/blog-articles"
|
||||
import { AppContent } from "@/components/ui/app-content"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { getArticles } from "@/lib/blog"
|
||||
import { Metadata } from "next"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -9,14 +10,30 @@ export const metadata: Metadata = {
|
||||
description: "",
|
||||
}
|
||||
|
||||
const BlogPage = async ({ params: { lang } }: any) => {
|
||||
interface BlogPageProps {
|
||||
params: { lang: string }
|
||||
searchParams?: { [key: string]: string | string[] | undefined }
|
||||
}
|
||||
|
||||
const BlogPage = async ({ params: { lang }, searchParams }: BlogPageProps) => {
|
||||
const { t } = await useTranslation(lang, "blog-page")
|
||||
|
||||
// Get the tag from searchParams
|
||||
const tag = searchParams?.tag as string | undefined
|
||||
|
||||
// Fetch articles, filtering by tag if present
|
||||
const articles = await getArticles({ tag })
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full bg-cover-gradient border-b border-tuatara-300">
|
||||
<div className="w-full bg-page-header-gradient">
|
||||
<AppContent className="flex flex-col gap-4 py-10 w-full">
|
||||
<Label.PageTitle label={t("title")} />
|
||||
{tag && (
|
||||
<h2 className="text-xl font-semibold text-tuatara-800">
|
||||
{`Filtered by tag: "${tag}"`}
|
||||
</h2>
|
||||
)}
|
||||
<h6 className="font-sans text-base font-normal text-tuatara-950 md:text-[18px] md:leading-[27px] md:max-w-[700px]">
|
||||
{t("subtitle")}
|
||||
</h6>
|
||||
@@ -24,7 +41,8 @@ const BlogPage = async ({ params: { lang } }: any) => {
|
||||
</div>
|
||||
|
||||
<AppContent className="flex flex-col gap-10 py-10">
|
||||
<BlogArticles />
|
||||
{/* Pass fetched articles and lang to the component */}
|
||||
<BlogArticles articles={articles} lang={lang} />
|
||||
</AppContent>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ export default async function ProjectsPage({ params: { lang } }: any) {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full bg-cover-gradient border-b border-tuatara-300">
|
||||
<div className="w-full bg-page-header-gradient">
|
||||
<AppContent className="flex flex-col gap-4 py-10 w-full">
|
||||
<Label.PageTitle label={t("title")} />
|
||||
<h6 className="font-sans text-base font-normal text-tuatara-950 md:text-[18px] md:leading-[27px] md:max-w-[700px]">
|
||||
|
||||
@@ -17,14 +17,9 @@ export const metadata: Metadata = {
|
||||
const ResearchPage = async ({ params: { lang } }: any) => {
|
||||
const { t } = await useTranslation(lang, "research-page")
|
||||
return (
|
||||
<div className="flex flex-col gap-10 lg:gap-32 pb-[128px]">
|
||||
<div
|
||||
className="w-full"
|
||||
style={{
|
||||
background: "linear-gradient(180deg, #C2E8F5 -17.44%, #FFF 62.5%)",
|
||||
}}
|
||||
>
|
||||
<AppContent className="flex flex-col gap-4 pt-10 w-full lg:px-[200px]">
|
||||
<div className="flex flex-col gap-10 lg:gap-32 pb-[128px] ">
|
||||
<div className="w-full bg-page-header-gradient">
|
||||
<AppContent className="flex flex-col gap-4 py-10 w-full">
|
||||
<Label.PageTitle label={t("title")} />
|
||||
<h6 className="font-sans text-base font-normal text-tuatara-950 md:text-[18px] md:leading-[27px] md:max-w-[700px]">
|
||||
{t("subtitle")}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
import { LangProps } from "@/types/common"
|
||||
@@ -173,21 +172,13 @@ export default function ResourcePage({ params: { lang } }: LangProps) {
|
||||
const { t: common } = useTranslation(lang, "common")
|
||||
|
||||
return (
|
||||
<Divider.Section className="bg-white">
|
||||
<PageHeader
|
||||
title={t("title")}
|
||||
subtitle={t("subtitle")}
|
||||
image={
|
||||
<div className="m-auto flex h-[320px] w-full max-w-[280px] items-center justify-center md:m-0 md:h-full md:w-full lg:max-w-[343px]">
|
||||
<Image
|
||||
width={343}
|
||||
height={320}
|
||||
src="/icons/resource-illustration.webp"
|
||||
alt="illustrations"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full bg-page-header-gradient">
|
||||
<AppContent className="flex flex-col gap-4 py-10 w-full">
|
||||
<Label.PageTitle label={t("title")} />
|
||||
<h6 className="font-sans text-base font-normal text-tuatara-950 md:text-[18px] md:leading-[27px] md:max-w-[700px]">
|
||||
{t("subtitle")}
|
||||
</h6>
|
||||
<Link
|
||||
href={siteConfig.addGithubResource}
|
||||
target="_blank"
|
||||
@@ -204,58 +195,60 @@ export default function ResourcePage({ params: { lang } }: LangProps) {
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
<div className="flex justify-center">
|
||||
<AppContent className="grid grid-cols-1 gap-6 py-10 md:grid-cols-[3fr_1fr] md:pb-20 lg:grid-cols-[4fr_1fr]">
|
||||
<div className="flex flex-col gap-6">
|
||||
<article className="flex flex-col space-y-8 ">
|
||||
<ResourcesContent
|
||||
components={{
|
||||
ResourceItem: (props: ResourceItemProps) => (
|
||||
<ResourceItem {...props} />
|
||||
),
|
||||
ResourceCard: (props: ResourceCardProps) => (
|
||||
<ResourceCard {...props} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</article>
|
||||
</div>
|
||||
<section className="relative hidden md:block ">
|
||||
<div className="sticky right-0 top-16 ml-auto">
|
||||
<ResourceNav lang={lang} />
|
||||
</div>
|
||||
</section>
|
||||
</AppContent>
|
||||
</div>
|
||||
<Banner
|
||||
title={
|
||||
<h3 className="py-2 font-display text-[18px] font-bold text-tuatara-950 md:text-3xl">
|
||||
{t("addResourceQuestion")}
|
||||
</h3>
|
||||
}
|
||||
>
|
||||
<Link
|
||||
href={siteConfig.links.discord}
|
||||
className="pb-6"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
passHref
|
||||
>
|
||||
<Button>
|
||||
<div className="flex items-center gap-1">
|
||||
<Icons.discord size={18} />
|
||||
<span className="text-[14px] uppercase">
|
||||
{common("connectWithUsOnPlatform", {
|
||||
platform: "Discord",
|
||||
})}
|
||||
</span>
|
||||
<Icons.externalUrl size={20} />
|
||||
<Divider.Section className="bg-white">
|
||||
<div className="flex justify-center">
|
||||
<AppContent className="grid grid-cols-1 gap-6 py-10 md:grid-cols-[3fr_1fr] md:pb-20 lg:grid-cols-[4fr_1fr]">
|
||||
<div className="flex flex-col gap-6">
|
||||
<article className="flex flex-col space-y-8 ">
|
||||
<ResourcesContent
|
||||
components={{
|
||||
ResourceItem: (props: ResourceItemProps) => (
|
||||
<ResourceItem {...props} />
|
||||
),
|
||||
ResourceCard: (props: ResourceCardProps) => (
|
||||
<ResourceCard {...props} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</article>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</Banner>
|
||||
</Divider.Section>
|
||||
<section className="relative hidden md:block ">
|
||||
<div className="sticky right-0 top-16 ml-auto">
|
||||
<ResourceNav lang={lang} />
|
||||
</div>
|
||||
</section>
|
||||
</AppContent>
|
||||
</div>
|
||||
<Banner
|
||||
title={
|
||||
<h3 className="py-2 font-display text-[18px] font-bold text-tuatara-950 md:text-3xl">
|
||||
{t("addResourceQuestion")}
|
||||
</h3>
|
||||
}
|
||||
>
|
||||
<Link
|
||||
href={siteConfig.links.discord}
|
||||
className="pb-6"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
passHref
|
||||
>
|
||||
<Button>
|
||||
<div className="flex items-center gap-1">
|
||||
<Icons.discord size={18} />
|
||||
<span className="text-[14px] uppercase">
|
||||
{common("connectWithUsOnPlatform", {
|
||||
platform: "Discord",
|
||||
})}
|
||||
</span>
|
||||
<Icons.externalUrl size={20} />
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</Banner>
|
||||
</Divider.Section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ image: "/articles/my-new-article/cover.webp" # Image used as cover
|
||||
tldr: "A brief summary of your article" #Short summary
|
||||
date: "YYYY-MM-DD" # Publication date in ISO format
|
||||
canonical: "mirror.xyz/my-new-article" # (Optional) The original source URL, this tells search engines the primary version of the content
|
||||
tags: ["tag1", "tag2"] # (Optional) Add relevant tags as an array of strings to categorize the article
|
||||
---
|
||||
```
|
||||
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import { Article, getArticles } from "@/lib/blog"
|
||||
import { Article } from "@/lib/blog"
|
||||
import Link from "next/link"
|
||||
import { BlogArticleCard } from "./blog-article-card"
|
||||
|
||||
export const BlogArticles = () => {
|
||||
const articles = getArticles()
|
||||
interface BlogArticlesProps {
|
||||
articles: Article[]
|
||||
lang: string // Add lang prop for correct linking
|
||||
}
|
||||
|
||||
export const BlogArticles = ({ articles, lang }: BlogArticlesProps) => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{articles.length === 0 && (
|
||||
<p className="col-span-full text-center text-gray-500">
|
||||
No articles found for this tag.
|
||||
</p>
|
||||
)}
|
||||
{articles.map(
|
||||
({ id, title, image, tldr = "", date, authors, content }: Article) => {
|
||||
const url = `/blog/${id}`
|
||||
// Use lang parameter for correct article URL
|
||||
const url = `/${lang}/blog/${id}`
|
||||
return (
|
||||
<Link
|
||||
className="flex-1 w-full h-full group hover:opacity-90 transition-opacity duration-300 rounded-xl overflow-hidden bg-white shadow-sm border border-slate-900/10"
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Button } from "../ui/button"
|
||||
import { Icons } from "../icons"
|
||||
|
||||
export async function BlogRecentArticles({ lang }: { lang: any }) {
|
||||
const articles = getArticles(5)
|
||||
const articles = getArticles({ limit: 5 })
|
||||
const { t } = await useTranslation(lang, "blog-page")
|
||||
|
||||
const lastArticle = articles[0]
|
||||
|
||||
@@ -11,6 +11,7 @@ type PageHeaderProps = {
|
||||
image?: ReactNode
|
||||
contentWidth?: number
|
||||
showDivider?: boolean
|
||||
size?: "small" | "large"
|
||||
}
|
||||
|
||||
const PageHeader = ({
|
||||
@@ -20,6 +21,7 @@ const PageHeader = ({
|
||||
children,
|
||||
image,
|
||||
showDivider = true,
|
||||
size = "small",
|
||||
}: PageHeaderProps) => {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center bg-cover-gradient md:h-[600px]">
|
||||
@@ -27,7 +29,7 @@ const PageHeader = ({
|
||||
<div className="flex flex-col items-start justify-between gap-10 md:flex-row md:gap-28">
|
||||
<div className="flex w-full flex-col justify-center gap-6 md:max-w-[700px] md:gap-8 lg:gap-14">
|
||||
<div className="flex flex-col gap-4 md:gap-8">
|
||||
<Label.PageTitle label={title} />
|
||||
<Label.PageTitle label={title} size={size} />
|
||||
{subtitle && (
|
||||
<h6 className="font-sans text-base font-normal text-tuatara-950 md:text-[18px] md:leading-[27px]">
|
||||
{subtitle}
|
||||
|
||||
@@ -55,7 +55,7 @@ export const ResearchList = ({ lang }: LangProps["params"]) => {
|
||||
|
||||
if (!isMounted) {
|
||||
return (
|
||||
<div className="flex flex-col gap-10 lg:px-[100px]">
|
||||
<div className="flex flex-col gap-10">
|
||||
<div className="flex flex-col gap-6 overflow-hidden">
|
||||
<div
|
||||
className={cn(
|
||||
@@ -90,7 +90,7 @@ export const ResearchList = ({ lang }: LangProps["params"]) => {
|
||||
<div className="relative grid items-start justify-between grid-cols-1">
|
||||
<div
|
||||
data-section="active-researchs"
|
||||
className="flex flex-col justify-between gap-10 lg:px-[100px]"
|
||||
className="flex flex-col justify-between gap-10"
|
||||
>
|
||||
<div className={cn("flex w-full flex-col gap-10")}>
|
||||
{!hasActiveFilters && (
|
||||
|
||||
@@ -22,7 +22,7 @@ export const HomepageHeader = ({ lang }: { lang: any }) => {
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 0.8, cubicBezier: "easeOut" }}
|
||||
>
|
||||
<Label.PageTitle label={t("headerTitle")} />
|
||||
<Label.PageTitle size="large" label={t("headerTitle")} />
|
||||
</motion.h1>
|
||||
}
|
||||
subtitle={t("headerSubtitle")}
|
||||
|
||||
@@ -25,6 +25,7 @@ const buttonVariants = cva(
|
||||
},
|
||||
size: {
|
||||
default: "h-10 py-2 px-4",
|
||||
xs: "h-8 px-2 rounded-md",
|
||||
sm: "h-9 px-3 rounded-md",
|
||||
lg: "h-11 px-8 rounded-md",
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import { cn } from "@/lib/utils"
|
||||
interface LabelProps {
|
||||
label: React.ReactNode
|
||||
className?: string
|
||||
size?: "small" | "large"
|
||||
}
|
||||
|
||||
const SectionTitle = ({ label, className = "" }: LabelProps) => {
|
||||
@@ -18,11 +19,16 @@ const SectionTitle = ({ label, className = "" }: LabelProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
const MainPageTitle = ({ label, className = "" }: LabelProps) => {
|
||||
const MainPageTitle = ({
|
||||
label,
|
||||
className = "",
|
||||
size = "small",
|
||||
}: LabelProps) => {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"text-4xl font-bold break-words font-display text-tuatara-950 lg:text-6xl xl:text-7xl",
|
||||
"text-4xl font-bold break-words font-display text-tuatara-950 ",
|
||||
size === "small" ? "lg:text-5xl" : "lg:text-6xl xl:text-7xl",
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
34
lib/blog.ts
34
lib/blog.ts
@@ -15,12 +15,14 @@ export interface Article {
|
||||
publicKey?: string
|
||||
hash?: string
|
||||
canonical?: string
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
const articlesDirectory = path.join(process.cwd(), "articles")
|
||||
|
||||
// Get all articles from /articles
|
||||
export function getArticles(limit: number = 1000) {
|
||||
export function getArticles(options?: { limit?: number; tag?: string }) {
|
||||
const { limit = 1000, tag } = options ?? {}
|
||||
// Get file names under /articles
|
||||
const fileNames = fs.readdirSync(articlesDirectory)
|
||||
const allArticlesData = fileNames.map((fileName: string) => {
|
||||
@@ -53,9 +55,18 @@ export function getArticles(limit: number = 1000) {
|
||||
},
|
||||
})
|
||||
|
||||
// Ensure tags are always an array, combining 'tags' and 'tag'
|
||||
const tags = [
|
||||
...(Array.isArray(matterResult.data?.tags)
|
||||
? matterResult.data.tags
|
||||
: []),
|
||||
...(matterResult.data?.tag ? [matterResult.data.tag] : []),
|
||||
]
|
||||
|
||||
return {
|
||||
id,
|
||||
...matterResult.data,
|
||||
tags: tags, // Assign the combined and normalized tags array
|
||||
content: matterResult.content,
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -66,14 +77,23 @@ export function getArticles(limit: number = 1000) {
|
||||
title: `Error processing ${id}`,
|
||||
content: "This article could not be processed due to an error.",
|
||||
date: new Date().toISOString().split("T")[0],
|
||||
tags: [],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let filteredArticles = allArticlesData.filter(Boolean) as Article[]
|
||||
|
||||
// Filter by tag if provided
|
||||
if (tag) {
|
||||
filteredArticles = filteredArticles.filter((article) =>
|
||||
article.tags?.includes(tag)
|
||||
)
|
||||
}
|
||||
|
||||
// Sort posts by date
|
||||
return allArticlesData
|
||||
.filter(Boolean)
|
||||
.sort((a: any, b: any) => {
|
||||
return filteredArticles
|
||||
.sort((a, b) => {
|
||||
const dateA = new Date(a.date)
|
||||
const dateB = new Date(b.date)
|
||||
|
||||
@@ -81,11 +101,13 @@ export function getArticles(limit: number = 1000) {
|
||||
return dateB.getTime() - dateA.getTime()
|
||||
})
|
||||
.slice(0, limit)
|
||||
.filter((article: any) => article.id !== "_article-template") as Article[]
|
||||
.filter((article) => article.id !== "_article-template")
|
||||
}
|
||||
|
||||
export function getArticleById(slug?: string) {
|
||||
const articles = getArticles()
|
||||
// Note: This might need adjustment if you expect getArticleById to also have tags
|
||||
// Currently relies on the base getArticles() which fetches all tags
|
||||
const articles = getArticles() // Fetch all articles to find the one by ID
|
||||
|
||||
return articles.find((article) => article.id === slug)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ module.exports = {
|
||||
"linear-gradient(180deg, #C2E8F5 -17.44%, #FFF 17.72%)",
|
||||
"research-card-gradient":
|
||||
"linear-gradient(84deg, #FFF -1.95%, #EAFAFF 59.98%, #FFF 100.64%)",
|
||||
"page-header-gradient":
|
||||
"linear-gradient(180deg, #C2E8F5 -17.44%, #FFF 62.5%)",
|
||||
},
|
||||
colors: {
|
||||
corduroy: "#4A5754",
|
||||
|
||||
Reference in New Issue
Block a user