Skip to content
Merged
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
10 changes: 3 additions & 7 deletions app/components/Contribute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,13 @@ import styles from "./Contribute.module.css";
import { TreeSelect } from "antd";
import type { DefaultOptionType } from "antd/es/select";
import { DataNode } from "antd/es/tree";

const REPO_OWNER = "InvolutionHell";
const REPO_NAME = "involutionhell.github.io";
const DEFAULT_BRANCH = "main";
const DOCS_BASE = "app/docs";
import { buildDocsNewUrl } from "@/lib/github";

type DirNode = { name: string; path: string; children?: DirNode[] };

// 统一调用工具函数生成 GitHub 新建链接,路径规则与 Edit 按钮一致
function buildGithubNewUrl(dirPath: string, filename: string, title: string) {
const file = filename.endsWith(".mdx") ? filename : `${filename}.mdx`;
const fullDir = `${DOCS_BASE}/${dirPath}`.replace(/\/+/g, "/");
const frontMatter = `---
title: ${title || "New Article"}
description:
Expand All @@ -42,7 +38,7 @@ tags: []
Write your content here.
`;
const params = new URLSearchParams({ filename: file, value: frontMatter });
return `https://github.com/${REPO_OWNER}/${REPO_NAME}/new/${DEFAULT_BRANCH}/${encodeURIComponent(fullDir)}?${params.toString()}`;
return buildDocsNewUrl(dirPath, params);
}

// ✅ 用纯文本 label + 一级节点 selectable:false
Expand Down
25 changes: 25 additions & 0 deletions app/components/EditOnGithub.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Link from "next/link";

// 复用的编辑链接按钮,统一封装图标与样式
interface EditOnGithubProps {
href: string;
}

export function EditOnGithub({ href }: EditOnGithubProps) {
return (
<Link
href={href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center rounded-md border border-transparent w-9 h-9 text-muted-foreground transition-colors hover:bg-muted/80 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
aria-label="Edit on GitHub"
>
<span
aria-hidden
Copy link

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

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

The aria-hidden attribute should have a value. It should be aria-hidden="true" to properly hide the decorative icon from screen readers.

Suggested change
aria-hidden
aria-hidden="true"

Copilot uses AI. Check for mistakes.
className="material-symbols-outlined text-lg flex items-center justify-center"
>
edit
</span>
</Link>
);
}
20 changes: 11 additions & 9 deletions app/docs/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +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 { EditOnGithub } from "@/app/components/EditOnGithub";
import { buildDocsEditUrl, getContributors } from "@/lib/github";
import { Contributors } from "@/app/components/Contributors";

interface Param {
Expand All @@ -21,25 +22,26 @@ export default async function DocPage({ params }: Param) {
notFound();
}

// 统一通过工具函数生成 Edit 链接,内部已处理中文目录编码
const editUrl = buildDocsEditUrl(page.path);
// 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 (
<DocsPage toc={page.data.toc}>
<DocsBody>
<h1 className="mb-4 text-3xl font-extrabold tracking-tight md:text-4xl">
{page.data.title}
</h1>
<div className="mb-6 flex flex-col gap-3 border-b border-border pb-6 md:mb-8 md:flex-row md:items-center md:justify-between">
<h1 className="text-3xl font-extrabold tracking-tight md:text-4xl">
{page.data.title}
</h1>
<EditOnGithub href={editUrl} />
</div>
<Mdx components={getMDXComponents()} />
<GiscusComments className="mt-16" />
<Contributors contributors={contributors} />
<section className="mt-16">
<GiscusComments />
</section>
</DocsBody>
</DocsPage>
);
Expand Down
5 changes: 5 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export default function RootLayout({
<html lang="en" suppressHydrationWarning>
<head>
<link rel="preload" href="/mascot.webp" as="image" type="image/webp" />
{/* 谷歌图标字体用于 Edit 按钮的 material symbol */}
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=edit"
/>
</head>
<body
suppressHydrationWarning
Expand Down
58 changes: 58 additions & 0 deletions lib/github.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,63 @@
import { cache } from "react";

// GitHub 相关工具方法:集中维护仓库常量与文档路径生成逻辑
const GITHUB_OWNER = "InvolutionHell";
const GITHUB_REPO = "involutionhell.github.io";
const DEFAULT_BRANCH = "main";
const DOCS_BASE = "app/docs";

const REPO_BASE_URL = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}`;

// 拼接路径并清理多余斜杠,避免出现 // 或首尾斜杠
function joinPath(...segments: (string | undefined)[]) {
return segments
.filter((segment) => (segment ?? "").trim().length > 0)
.join("/")
.replace(/\/+/g, "/")
.replace(/^\/+/, "")
.replace(/\/+$/, "");
}

// 将路径逐段 URL 编码,处理中文等特殊字符
function encodeRepoPath(...segments: (string | undefined)[]) {
const joined = joinPath(...segments);
if (!joined) return "";
return joined
.split("/")
.map((segment) => encodeURIComponent(segment))
.join("/");
}

// 构建文档的 GitHub 编辑链接
export function buildDocsEditUrl(relativeDocPath: string) {
const encoded = encodeRepoPath(DOCS_BASE, relativeDocPath);
return `${REPO_BASE_URL}/edit/${DEFAULT_BRANCH}/${encoded}`;
}

// 构建在 GitHub 新建文档的链接,附带 frontmatter 参数
export function buildDocsNewUrl(relativeDir: string, params: URLSearchParams) {
const encodedDir = encodeRepoPath(DOCS_BASE, relativeDir);
const query = params.toString();
const suffix = query ? `?${query}` : "";
return `${REPO_BASE_URL}/new/${DEFAULT_BRANCH}/${encodedDir}${suffix}`;
}

// 帮助预览完整 docs 路径(未编码)
export function normalizeDocsPath(relative: string) {
return joinPath(DOCS_BASE, relative);
}

// 暴露常量给其他场景复用
export const githubConstants = {
owner: GITHUB_OWNER,
repo: GITHUB_REPO,
defaultBranch: DEFAULT_BRANCH,
docsBase: DOCS_BASE,
repoBaseUrl: REPO_BASE_URL,
};



// Define contributor data structure
interface Contributor {
login: string;
Expand Down