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
96 changes: 2 additions & 94 deletions web/app/[locale]/docs/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import Link from "next/link";
import {
getTopicsByCategory,
REPO_DOCS_BASE,
type DocTopic,
} from "@/lib/docs-map";
import { DocsSearch } from "@/components/docs-search";
import { buildPageMetadata } from "@/lib/page-meta";

export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
Expand All @@ -19,94 +14,7 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
});
}

/* ------------------------------------------------------------------ */
/* Locale-aware strings */
/* ------------------------------------------------------------------ */

const CATEGORY_LABELS: Record<string, { en: string; zh: string }> = {
"getting-started": { en: "Getting started", zh: "入门" },
"core-concepts": { en: "Core concepts", zh: "核心概念" },
reference: { en: "Reference", zh: "参考" },
extending: { en: "Extending", zh: "扩展" },
operations: { en: "Operations & community", zh: "运维与社区" },
};

/* ------------------------------------------------------------------ */

function topicHref(topic: DocTopic, locale: string): string {
if (topic.hasPage) {
return `/${locale}/docs/${topic.slug}`;
}
const src = Array.isArray(topic.repoSource) ? topic.repoSource[0] : topic.repoSource;
return `${REPO_DOCS_BASE}/${src}`;
}

function topicSources(topic: DocTopic): string[] {
return Array.isArray(topic.repoSource) ? topic.repoSource : [topic.repoSource];
}

/* ------------------------------------------------------------------ */

export default async function DocsHubPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
const isZh = locale === "zh";
const byCategory = getTopicsByCategory();

const body = (
<div className="space-y-12">
{[...byCategory.entries()].map(([cat, topics]) => (
<section key={cat} id={cat}>
<h2 className="font-display text-2xl mb-1">
{isZh ? CATEGORY_LABELS[cat]?.zh ?? cat : CATEGORY_LABELS[cat]?.en ?? cat}
</h2>
<div className="grid sm:grid-cols-2 gap-4 mt-4">
{topics.map((t) => {
const href = topicHref(t, locale);
const sources = topicSources(t);
const isExternal = !t.hasPage;
return (
<Link
key={t.id}
href={href}
target={isExternal ? "_blank" : undefined}
className="hairline-t hairline-b hairline-l hairline-r p-4 hover:bg-paper-deep transition-colors group block"
>
<div className="flex items-center gap-2 mb-1.5">
<span className="font-mono text-[0.62rem] uppercase tracking-widest text-ink-mute">
{isZh ? t.label.zh : t.label.en}
</span>
{isExternal && (
<span className="font-mono text-[0.6rem] text-ink-mute">↗</span>
)}
</div>
<p className="text-sm text-ink-soft leading-relaxed">
{isZh ? t.description.zh : t.description.en}
</p>
<div className="mt-2 font-mono text-[0.62rem] text-ink-mute truncate">
{sources.map((s, i) => (
<span key={s}>
{i > 0 && ", "}
{s}
</span>
))}
</div>
</Link>
);
})}
</div>
</section>
))}

{/* Parapgraph about the docs approach */}
<section className="hairline-t pt-8">
<p className="text-sm text-ink-mute leading-relaxed max-w-2xl">
{isZh
? "§ 标记的条目在 CodeWhale 网站上有独立页面;↗ 标记的条目链接到 GitHub 仓库中的源文档。所有内容来源于 docs/ 目录下的 40+ 篇 Markdown 文档,通过 docs-map.ts 注册表维护。"
: "Entries marked § have dedicated pages on codewhale.net; entries marked ↗ link to source documents in the GitHub repository. All content is sourced from 40+ Markdown documents in the docs/ directory, maintained through the docs-map.ts registry."}
</p>
</section>
</div>
);

return body;
return <DocsSearch locale={locale} />;
}
30 changes: 2 additions & 28 deletions web/app/[locale]/faq/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Link from "next/link";
import { Seal } from "@/components/seal";
import { FaqSearch } from "@/components/faq-search";

export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
Expand Down Expand Up @@ -732,34 +733,7 @@ export default async function FaqPage({ params }: { params: Promise<{ locale: st
</section>

<section className="mx-auto max-w-[1400px] px-6 pb-20">
<div className="space-y-0 hairline-t hairline-b">
{items.map((item, i) => (
<details key={i} className="group hairline-b last:border-b-0">
<summary className="px-0 py-5 cursor-pointer flex items-start gap-4 hover:text-indigo transition-colors">
<span className="font-mono text-indigo tabular text-sm pt-0.5 shrink-0">
{String(i + 1).padStart(2, "0")}
</span>
<span className="font-display text-lg leading-snug flex-1">{item.q}</span>
<span className="font-mono text-ink-mute text-sm group-open:rotate-45 transition-transform shrink-0">+</span>
</summary>
<div className="pb-5 pl-10 pr-4">
<div className={`text-ink-soft leading-relaxed ${isZh ? "leading-[1.9] tracking-wide" : ""}`}>
{item.a}
</div>
{item.sources && item.sources.length > 0 && (
<div className="mt-3 flex items-center gap-2 flex-wrap">
<span className="font-mono text-[0.66rem] text-ink-mute uppercase tracking-wider">
{isZh ? "来源" : "Sources"}:
</span>
{item.sources.map((s) => (
<span key={s} className="font-mono text-[0.7rem] text-indigo">{s}</span>
))}
</div>
)}
</div>
</details>
))}
</div>
<FaqSearch items={items} locale={locale} />

<div className="mt-12 text-center">
<p className="text-ink-soft text-sm mb-4">
Expand Down
25 changes: 25 additions & 0 deletions web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,31 @@ code.inline {
border-radius: 2px;
}


/* ---------- search ---------- */
.search-input {
font-family: var(--font-body), system-ui, sans-serif;
font-size: 1rem;
padding: 0.75rem 2.5rem 0.75rem 1rem;
background: var(--paper);
color: var(--ink);
border: 1px solid var(--hairline);
transition: border-color 180ms ease, box-shadow 180ms ease;
}
.search-input::placeholder { color: var(--ink-mute); }
.search-input:focus {
outline: none;
border-color: var(--indigo);
box-shadow: 0 0 0 3px var(--indigo-pale);
}

mark.search-highlight {
background: var(--indigo-pale);
color: var(--indigo-deep);
padding: 0.02em 0.08em;
border-radius: 2px;
}

/* ---------- nav link ---------- */
.nav-link {
font-family: var(--font-mono), monospace;
Expand Down
Loading
Loading