Skip to content

Commit c6c0509

Browse files
Merge pull request #382 from nocodb/tags-new-pages
2 parents 94c6513 + 1a3c572 commit c6c0509

File tree

7 files changed

+211
-5
lines changed

7 files changed

+211
-5
lines changed

app/docs/product-docs/[[...slug]]/page.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import ClerkTOCItems from "fumadocs-ui/components/layout/toc-clerk";
66
import { PageTOC } from "fumadocs-ui/layouts/docs/page";
77
import { DocsBody, DocsDescription, DocsTitle } from "fumadocs-ui/page";
88
import { notFound } from "next/navigation";
9+
import Link from "next/link";
10+
import { Tag } from "lucide-react";
911
import TOCMobile from "@/components/layout/TOCMobile";
1012
import MdxLink from "@/components/mdx/MdxLink";
1113
import { source } from "@/lib/source";
@@ -21,6 +23,7 @@ export default async function Page(props: {
2123
}
2224

2325
const MDXContent = page.data.body;
26+
const tags = (page.data as any).tags as string[] | undefined;
2427

2528
return (
2629
<TOCProvider toc={page.data.toc}>
@@ -43,6 +46,23 @@ export default async function Page(props: {
4346
})}
4447
/>
4548
</DocsBody>
49+
50+
{tags && tags.length > 0 && (
51+
<div className="flex flex-wrap items-center gap-2 pt-2">
52+
<Tag className="h-4 w-4 text-muted-foreground" />
53+
<span className="text-sm text-muted-foreground">Tags:</span>
54+
{tags.map((tag) => (
55+
<Link
56+
key={tag}
57+
href={`/docs/tags/${tag.toLowerCase().replace(/\s+/g, "-")}`}
58+
className="inline-flex items-center rounded-md border border-nc-border-grey-light bg-nc-background-default px-2.5 py-0.5 text-sm font-medium transition-colors hover:border-nc-border-grey hover:bg-nc-background-grey-light hover:text-nc-content-brand"
59+
>
60+
{tag}
61+
</Link>
62+
))}
63+
</div>
64+
)}
65+
4666
<Cards>
4767
{getPageTreePeers(source.pageTree, page.url)
4868
.slice(0, 2)

app/docs/tags/[tag]/page.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Card, Cards } from "fumadocs-ui/components/card";
2+
import { notFound } from "next/navigation";
3+
import { source } from "@/lib/source";
4+
5+
export default async function TagPage(props: {
6+
params: Promise<{ tag: string }>;
7+
}) {
8+
const params = await props.params;
9+
const tagSlug = decodeURIComponent(params.tag);
10+
11+
const pages = source.getPages().filter((page) => {
12+
const tags = (page.data as any).tags as string[] | undefined;
13+
return tags?.some((t) => t.toLowerCase().replace(/\s+/g, "-") === tagSlug.toLowerCase());
14+
});
15+
16+
// Find the actual tag name (with proper casing) from the first matching page
17+
const actualTag = pages.length > 0
18+
? ((pages[0].data as any).tags as string[] | undefined)?.find(
19+
(t) => t.toLowerCase().replace(/\s+/g, "-") === tagSlug.toLowerCase()
20+
) || tagSlug
21+
: tagSlug;
22+
23+
if (pages.length === 0) {
24+
notFound();
25+
}
26+
27+
return (
28+
<div className="container relative mx-auto flex max-w-179 flex-1 shrink-1 flex-col gap-8 overflow-y-auto p-4 pt-32">
29+
<div>
30+
<h1 className="text-4xl font-bold mb-2">{actualTag}</h1>
31+
<p className="text-muted-foreground">
32+
{pages.length} {pages.length === 1 ? "document" : "documents"} tagged with "{actualTag}"
33+
</p>
34+
</div>
35+
36+
<Cards>
37+
{pages.map((page) => (
38+
<Card
39+
key={page.url}
40+
href={page.url}
41+
title={page.data.title}
42+
>
43+
{page.data.description}
44+
</Card>
45+
))}
46+
</Cards>
47+
</div>
48+
);
49+
}
50+
51+
export async function generateStaticParams() {
52+
const allTags = new Set<string>();
53+
54+
source.getPages().forEach((page) => {
55+
const tags = (page.data as any).tags as string[] | undefined;
56+
tags?.forEach((tag) => allTags.add(tag));
57+
});
58+
59+
return Array.from(allTags).map((tag) => ({
60+
tag: tag.toLowerCase().replace(/\s+/g, "-"),
61+
}));
62+
}
63+
64+
export async function generateMetadata(props: {
65+
params: Promise<{ tag: string }>;
66+
}) {
67+
const params = await props.params;
68+
const tagSlug = decodeURIComponent(params.tag);
69+
70+
// Find the actual tag name with proper casing
71+
const pages = source.getPages();
72+
let actualTag = tagSlug;
73+
74+
for (const page of pages) {
75+
const tags = (page.data as any).tags as string[] | undefined;
76+
const found = tags?.find((t) => t.toLowerCase().replace(/\s+/g, "-") === tagSlug.toLowerCase());
77+
if (found) {
78+
actualTag = found;
79+
break;
80+
}
81+
}
82+
83+
return {
84+
title: `Tag: ${actualTag}`,
85+
description: `All documentation pages tagged with "${actualTag}"`,
86+
};
87+
}

app/docs/tags/page.tsx

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import Link from "next/link";
2+
import { source } from "@/lib/source";
3+
4+
interface TagInfo {
5+
name: string;
6+
count: number;
7+
slug: string;
8+
}
9+
10+
export default function TagsIndexPage() {
11+
// Collect all tags with their counts, normalizing case-insensitive duplicates
12+
const tagMap = new Map<string, { name: string; count: number }>();
13+
14+
source.getPages().forEach((page) => {
15+
const tags = (page.data as any).tags as string[] | undefined;
16+
tags?.forEach((tag) => {
17+
const slug = tag.toLowerCase().replace(/\s+/g, "-");
18+
const existing = tagMap.get(slug);
19+
20+
if (existing) {
21+
// Increment count, keep the first encountered name
22+
existing.count += 1;
23+
} else {
24+
tagMap.set(slug, { name: tag, count: 1 });
25+
}
26+
});
27+
});
28+
29+
const tags: TagInfo[] = Array.from(tagMap.entries())
30+
.map(([slug, { name, count }]) => ({
31+
name,
32+
count,
33+
slug,
34+
}))
35+
.sort((a, b) => a.name.localeCompare(b.name));
36+
37+
// Group tags alphabetically by first letter
38+
const groupedTags = tags.reduce((acc, tag) => {
39+
const firstLetter = tag.name[0].toUpperCase();
40+
if (!acc[firstLetter]) {
41+
acc[firstLetter] = [];
42+
}
43+
acc[firstLetter].push(tag);
44+
return acc;
45+
}, {} as Record<string, TagInfo[]>);
46+
47+
const letters = Object.keys(groupedTags).sort();
48+
49+
return (
50+
<div className="container relative mx-auto flex max-w-179 flex-1 shrink-1 flex-col gap-8 overflow-y-auto p-4 py-32">
51+
<div>
52+
<h1 className="text-4xl font-bold mb-2">All Tags</h1>
53+
<p className="text-muted-foreground">
54+
Browse documentation by tags
55+
</p>
56+
</div>
57+
58+
<div className="space-y-8">
59+
{letters.map((letter) => (
60+
<div key={letter} className="space-y-4">
61+
<h2 className="text-2xl font-semibold border-b pb-2">{letter}</h2>
62+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
63+
{groupedTags[letter].map((tag) => (
64+
<Link
65+
key={tag.slug}
66+
href={`/docs/tags/${tag.slug}`}
67+
className="group flex items-center justify-between rounded-lg border border-nc-border-grey-light bg-nc-background-default p-4 transition-colors hover:border-nc-border-grey hover:bg-nc-background-grey-light"
68+
>
69+
<span className="font-medium group-hover:text-nc-content-brand">
70+
{tag.name}
71+
</span>
72+
<span className="text-sm text-muted-foreground">
73+
{tag.count}
74+
</span>
75+
</Link>
76+
))}
77+
</div>
78+
</div>
79+
))}
80+
</div>
81+
</div>
82+
);
83+
}
84+
85+
export async function generateMetadata() {
86+
return {
87+
title: "All Tags",
88+
description: "Browse all documentation tags",
89+
};
90+
}

components/layout/TopBarNaigation.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ const tabs = [
3030

3131
export default function TopBarNaigation() {
3232
const pathname = usePathname();
33+
34+
if(pathname.startsWith("/docs/tags")) {
35+
return null;
36+
}
37+
3338
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
3439
const [activeIndex, setActiveIndex] = useState(-1);
3540
const [hoverStyle, setHoverStyle] = useState({});

netlify.toml

Lines changed: 0 additions & 5 deletions
This file was deleted.

scripts/generate-llm-content.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ class LLMContentGenerator {
165165
return null;
166166
}
167167

168+
// Skip tags URLs
169+
if (url.includes("/docs/tags")) {
170+
return null;
171+
}
172+
168173
console.log(`📖 Processing: ${url}`);
169174

170175
// Categorize URL

source.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import { z } from "zod";
1010
export const docs = defineDocs({
1111
dir: "content/docs",
1212
docs: {
13+
schema: frontmatterSchema.extend({
14+
tags: z.array(z.string()).optional(),
15+
keywords: z.array(z.string()).optional(),
16+
}),
1317
postprocess: {
1418
includeProcessedMarkdown: true,
1519
},

0 commit comments

Comments
 (0)