Skip to content
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
GITHUB_TOKEN=github_pat_xxxxxxxxxxxxxx
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,10 @@ next-env.d.ts
.source

# package管理
.package-lock.json
.package-lock.json

# Agents.md
Agents.md

# Environment variables
.env
47 changes: 47 additions & 0 deletions app/components/Contributors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Image from "next/image";
import Link from "next/link";

interface Contributor {
login: string;
avatar_url: string;
html_url: string;
}

export function Contributors({
contributors,
}: {
contributors: Contributor[];
}) {
if (contributors.length === 0) {
return null;
}

return (
<section aria-labelledby="contributors-heading">
<hr className="border-border/70 !mt-10 !mb-5" />
<h2 id="contributors-heading">贡献者</h2>
<ul className="mt-0 mb-0 flex flex-wrap items-center gap-x-6 gap-y-4 list-none p-0">
{contributors.map((contributor) => (
<li key={contributor.login}>
<Link
href={contributor.html_url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-3 text-base font-medium text-primary transition-colors hover:text-primary/80 no-underline"
>
<Image
src={contributor.avatar_url}
alt={contributor.login}
width={35}
height={35}
className="!m-0 h-10 w-10 rounded-full border border-border/50 object-cover shadow-sm"
/>
<span>{contributor.login}</span>
</Link>
</li>
))}
</ul>
<hr className="!mb-0 !mt-5 border-border/70" />
</section>
);
}
9 changes: 9 additions & 0 deletions app/docs/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { notFound } from "next/navigation";
import type { Metadata } from "next";
import { getMDXComponents } from "@/mdx-components";
import { GiscusComments } from "@/app/components/GiscusComments";
import { getContributors } from "@/lib/github";
import { Contributors } from "@/app/components/Contributors";

interface Param {
params: Promise<{
Expand All @@ -19,6 +21,12 @@ export default async function DocPage({ params }: Param) {
notFound();
}

// Get file path for contributors
const filePath = "app/docs/" + page.file.path;

// Fetch contributors data on server side
const contributors = await getContributors(filePath);

const Mdx = page.data.body;

return (
Expand All @@ -28,6 +36,7 @@ export default async function DocPage({ params }: Param) {
{page.data.title}
</h1>
<Mdx components={getMDXComponents()} />
<Contributors contributors={contributors} />
<section className="mt-16">
<GiscusComments />
</section>
Expand Down
19 changes: 0 additions & 19 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -203,25 +203,6 @@
--tracking-normal: 0em;
}

/* Force remove rounded corners globally for research style */
@layer base {
.rounded,
.rounded-sm,
.rounded-md,
.rounded-lg,
.rounded-xl,
.rounded-2xl,
.rounded-3xl,
.rounded-full,
.rounded-none {
border-radius: 0 !important;
}
}

/*
---break---
*/

.dark {
--background: oklch(0.2077 0.0398 265.7549);
--foreground: oklch(0.9288 0.0126 255.5078);
Expand Down
57 changes: 57 additions & 0 deletions lib/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { cache } from "react";

// Define contributor data structure
interface Contributor {
login: string;
avatar_url: string;
html_url: string;
}

// Use React's cache function for request caching and deduplication
export const getContributors = cache(
async (filePath: string): Promise<Contributor[]> => {
try {
// Use GITHUB_TOKEN environment variable for authorization to increase API rate limit
const headers: HeadersInit = {};
if (process.env.GITHUB_TOKEN) {
headers.Authorization = `token ${process.env.GITHUB_TOKEN}`;
}

const response = await fetch(
`https://api.github.com/repos/InvolutionHell/involutionhell.github.io/commits?path=${filePath}`,
{
headers,
// Use next.revalidate to configure cache strategy (e.g., revalidate every hour)
next: { revalidate: 3600 },
},
);

if (!response.ok) {
// If request fails, return empty array
console.error(
`Failed to fetch contributors for ${filePath}: ${response.statusText}`,
);
return [];
}

const commits = await response.json();

// Use Map to deduplicate contributors
const uniqueContributors = new Map<string, Contributor>();
commits.forEach((commit: { author?: Contributor }) => {
if (commit.author) {
uniqueContributors.set(commit.author.login, {
login: commit.author.login,
avatar_url: commit.author.avatar_url,
html_url: commit.author.html_url,
});
}
});

return Array.from(uniqueContributors.values());
} catch (error) {
console.error(`Error fetching contributors for ${filePath}:`, error);
return [];
}
},
);