mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat(kb): Add topics pages support (#49959)
This commit is contained in:
1 parent
0ab0bb8efd
commit
7e19cbe140
6 files changed
+98
-11
No files matched your search
@@ -9,15 +9,16 @@ import {
|
||||
navigationMenuTriggerStyle,
|
||||
} from 'ui'
|
||||
|
||||
import { TOPICS, topicToSlug } from '../lib/topics'
|
||||
|
||||
const topics = TOPICS.map((topic) => ({
|
||||
label: topic.name,
|
||||
href: `${import.meta.env.BASE_URL}/topics/${topicToSlug(topic.name)}`,
|
||||
}))
|
||||
|
||||
/**
|
||||
* Hard-codding links in here for now until we have actual content. Might be worth putting these arrays in their on data file too.
|
||||
*/
|
||||
const topics = [
|
||||
{ label: 'Troubleshooting', href: '#' },
|
||||
{ label: 'Migrations', href: '#' },
|
||||
{ label: 'Comparisons', href: '#' },
|
||||
]
|
||||
|
||||
const resources = [
|
||||
{ label: 'Status', href: 'https://status.supabase.com' },
|
||||
{ label: 'Changelog', href: 'https://supabase.com/changelog' },
|
||||
|
||||
@@ -2,6 +2,8 @@ import { defineCollection } from 'astro:content'
|
||||
import { glob } from 'astro/loaders'
|
||||
import { z } from 'astro/zod'
|
||||
|
||||
import { TOPIC_NAMES } from './lib/topics'
|
||||
|
||||
// Every entry here is rendered through GuideLayout by
|
||||
// src/pages/guides/[...slug].astro — dropping a new file in
|
||||
// src/content/guides doesn't need any per-file layout wiring.
|
||||
@@ -9,8 +11,10 @@ const guides = defineCollection({
|
||||
loader: glob({ pattern: '**/*.md', base: './src/content/guides' }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string().optional(),
|
||||
topics: z.array(z.string()).optional(),
|
||||
description: z.string(),
|
||||
// z.enum (not z.string) so a guide referencing a topic outside TOPICS
|
||||
// fails content validation instead of silently rendering an orphaned tag.
|
||||
topics: z.array(z.enum(TOPIC_NAMES)),
|
||||
github_url: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: 'Markdown elements sample'
|
||||
description: 'A lorem ipsum sample guide exercising every base Markdown element supported by the guide layout.'
|
||||
topics: ['example', 'markdown']
|
||||
topics: ['Tutorial', 'Database']
|
||||
github_url: 'https://github.com/supabase/supabase/discussions/0000000'
|
||||
---
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@ import { Github } from 'lucide-react'
|
||||
import { Badge } from 'ui'
|
||||
|
||||
import Layout from './Layout.astro'
|
||||
import { getTopicDescription, type Topic, topicToSlug } from '../lib/topics'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
description?: string
|
||||
topics?: string[]
|
||||
topics?: Topic[]
|
||||
github_url?: string
|
||||
}
|
||||
|
||||
@@ -21,7 +22,14 @@ const { title, description, topics = [], github_url } = Astro.props
|
||||
{
|
||||
topics.length > 0 && (
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{topics.map((topic) => <Badge>{topic}</Badge>)}
|
||||
{topics.map((topic) => (
|
||||
<a
|
||||
href={`${import.meta.env.BASE_URL}/topics/${topicToSlug(topic)}`}
|
||||
title={getTopicDescription(topic)}
|
||||
>
|
||||
<Badge>{topic}</Badge>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Canonical list of guide topics. Single source of truth for the `topics`
|
||||
// field in src/content.config.ts (so a guide with an unsupported topic fails
|
||||
// content validation), the "Topics" nav menu, and the /topics/[topic] pages.
|
||||
export const TOPICS = [
|
||||
{ name: 'Migration', description: 'Moving data, schemas, or projects onto Supabase' },
|
||||
{ name: 'Comparison', description: 'How Supabase compares to other databases and platforms' },
|
||||
{ name: 'Troubleshooting', description: 'Common errors and how to resolve them' },
|
||||
{ name: 'Tutorial', description: 'Step-by-step walkthroughs for building with Supabase' },
|
||||
{ name: 'Storage', description: 'Uploading, managing, and serving files' },
|
||||
{ name: 'Auth', description: 'Authentication, authorization, and user management' },
|
||||
{ name: 'Database', description: 'Postgres schemas, queries, and performance' },
|
||||
{ name: 'Edge Functions', description: 'Deploying and running serverless functions' },
|
||||
{ name: 'Queues', description: 'Background jobs and message processing' },
|
||||
{ name: 'Realtime', description: 'Broadcast, presence, and database changes' },
|
||||
{ name: 'Supabase Platform', description: 'Project settings, billing, and infrastructure' },
|
||||
] as const
|
||||
|
||||
export type Topic = (typeof TOPICS)[number]['name']
|
||||
|
||||
// zod's `enum()` needs a literal non-empty tuple of strings, which `.map()`
|
||||
// can't preserve on its own — this cast is safe because TOPICS is `as const`.
|
||||
export const TOPIC_NAMES = TOPICS.map((topic) => topic.name) as [Topic, ...Topic[]]
|
||||
|
||||
export function getTopicDescription(topic: Topic): string {
|
||||
return TOPICS.find((t) => t.name === topic)!.description
|
||||
}
|
||||
|
||||
// URL-safe slug for a topic, e.g. "Edge Functions" -> "edge-functions".
|
||||
export function topicToSlug(topic: Topic): string {
|
||||
return topic.toLowerCase().replace(/\s+/g, '-')
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
import Layout from '../../layouts/Layout.astro'
|
||||
import { TOPICS, topicToSlug } from '../../lib/topics'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const guides = await getCollection('guides')
|
||||
|
||||
// One page per topic in TOPICS — not just topics a guide already uses —
|
||||
// so every link on the "Topics" nav resolves instead of 404ing.
|
||||
return TOPICS.map((topic) => ({
|
||||
params: { topic: topicToSlug(topic.name) },
|
||||
props: {
|
||||
topic: topic.name,
|
||||
description: topic.description,
|
||||
guides: guides.filter((guide) => guide.data.topics?.includes(topic.name)),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
const { topic, description, guides } = Astro.props
|
||||
---
|
||||
|
||||
<Layout title={topic} description={description}>
|
||||
<article class="prose max-w-3xl mx-auto px-6 py-16">
|
||||
<h1>{topic}</h1>
|
||||
<p class="lead">{description}</p>
|
||||
{
|
||||
guides.length === 0 ? (
|
||||
<p>No guides for this topic</p>
|
||||
) : (
|
||||
<ul>
|
||||
{guides.map((guide) => (
|
||||
<li>
|
||||
<a href={`${import.meta.env.BASE_URL}/guides/${guide.id}`}>{guide.data.title}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</article>
|
||||
</Layout>
|
||||
Reference in new issue
Block a user