From 673b57c6269c1bb03936f5113e6b2ddad78cd409 Mon Sep 17 00:00:00 2001 From: Simon Vrachliotis Date: Wed, 24 Jul 2024 12:36:15 +1000 Subject: [PATCH] Make docs page hub and sidebar navigation editable with Keystatic --- docs/app/(site)/blog/page.tsx | 4 +- .../app/(site)/docs/[...rest]/page-client.tsx | 9 +- docs/app/(site)/docs/[...rest]/page.tsx | 24 +- .../docs/config/overview/page-client.tsx | 10 +- docs/app/(site)/docs/config/overview/page.tsx | 7 +- docs/app/(site)/docs/examples/page-client.tsx | 9 +- docs/app/(site)/docs/examples/page.tsx | 7 +- .../document-field-demo/page-client.tsx | 26 +- .../docs/guides/document-field-demo/page.tsx | 27 +- .../docs/guides/overview/page-client.tsx | 10 +- docs/app/(site)/docs/guides/overview/page.tsx | 7 +- docs/app/(site)/docs/page-client.tsx | 316 +--------------- docs/app/(site)/docs/page.tsx | 13 +- .../(site)/docs/walkthroughs/page-client.tsx | 10 +- docs/app/(site)/docs/walkthroughs/page.tsx | 7 +- docs/app/(site)/updates/page-client.tsx | 9 +- docs/app/(site)/updates/page.tsx | 11 +- .../(site)/updates/roadmap/page-client.tsx | 9 +- docs/app/(site)/updates/roadmap/page.tsx | 7 +- docs/components/Page.tsx | 91 +---- docs/components/docs/DocsFooter.tsx | 23 -- docs/components/docs/DocsLayout.tsx | 6 + docs/components/docs/DocsLayoutClient.tsx | 109 ++++++ docs/components/docs/ExamplesList.tsx | 159 -------- docs/components/docs/KeystoneExperience.tsx | 217 +++++++++++ docs/components/docs/Navigation.tsx | 107 +----- docs/components/docs/Sidebar.tsx | 5 +- docs/components/docs/WalkthroughsList.tsx | 59 --- .../docs/docs-navigation/client.tsx | 48 +++ .../components/docs/docs-navigation/index.tsx | 33 ++ docs/components/docs/featured-docs/client.tsx | 112 ++++++ docs/components/docs/featured-docs/index.tsx | 64 ++++ .../docs/featured-examples/client.tsx | 55 +++ .../docs/featured-examples/index.tsx | 41 ++ docs/content/docs/config/config.md | 1 - docs/content/docs/graphql/filters.md | 357 ++++++++++++++---- .../docs/guides/auth-and-access-control.md | 6 +- docs/content/docs/guides/cli.md | 20 +- .../docs/guides/custom-admin-ui-pages.md | 1 - docs/content/docs/guides/relationships.md | 47 ++- docs/content/docs/walkthroughs/lesson-1.md | 15 +- docs/content/docs/walkthroughs/lesson-2.md | 11 +- docs/content/docs/walkthroughs/lesson-3.md | 12 +- docs/content/docs/walkthroughs/lesson-4.md | 12 +- docs/content/docs/walkthroughs/lesson-5.md | 14 +- docs/content/examples/auth.yaml | 5 + docs/content/examples/azure.yaml | 5 + docs/content/examples/blog.yaml | 6 + .../examples/custom-admin-ui-logo.yaml | 5 + .../examples/custom-admin-ui-navigation.yaml | 5 + .../examples/custom-admin-ui-pages.yaml | 5 + docs/content/examples/custom-admin-ui.yaml | 5 + docs/content/examples/custom-field-type.yaml | 5 + docs/content/examples/custom-field-view.yaml | 5 + docs/content/examples/default-values.yaml | 8 + .../document-field-customisation.yaml | 5 + docs/content/examples/document-field.yaml | 6 + docs/content/examples/extend-express-app.yaml | 6 + .../examples/extend-graphql-schema-nexus.yaml | 6 + .../examples/extend-graphql-schema-ts.yaml | 6 + .../examples/extend-graphql-schema.yaml | 6 + docs/content/examples/heroku.yaml | 7 + docs/content/examples/json-field.yaml | 6 + docs/content/examples/next-js.yaml | 5 + docs/content/examples/railway.yaml | 7 + docs/content/examples/singleton.yaml | 5 + docs/content/examples/task-manager.yaml | 7 + docs/content/examples/testing.yaml | 5 + docs/content/examples/virtual-field.yaml | 5 + docs/content/featured-docs.yaml | 145 +++++++ docs/content/featured-examples.yaml | 23 ++ docs/content/navigation.yaml | 285 ++++++++++++++ docs/keystatic.config.tsx | 183 ++++++++- docs/keystatic/get-featured-docs-map.ts | 76 ++++ docs/keystatic/get-navigation-map.ts | 27 ++ docs/keystatic/gradient-selector.ts | 17 + docs/keystatic/keystatic-reader.ts | 4 + 77 files changed, 2065 insertions(+), 978 deletions(-) delete mode 100644 docs/components/docs/DocsFooter.tsx create mode 100644 docs/components/docs/DocsLayout.tsx create mode 100644 docs/components/docs/DocsLayoutClient.tsx delete mode 100644 docs/components/docs/ExamplesList.tsx create mode 100644 docs/components/docs/KeystoneExperience.tsx delete mode 100644 docs/components/docs/WalkthroughsList.tsx create mode 100644 docs/components/docs/docs-navigation/client.tsx create mode 100644 docs/components/docs/docs-navigation/index.tsx create mode 100644 docs/components/docs/featured-docs/client.tsx create mode 100644 docs/components/docs/featured-docs/index.tsx create mode 100644 docs/components/docs/featured-examples/client.tsx create mode 100644 docs/components/docs/featured-examples/index.tsx create mode 100644 docs/content/examples/auth.yaml create mode 100644 docs/content/examples/azure.yaml create mode 100644 docs/content/examples/blog.yaml create mode 100644 docs/content/examples/custom-admin-ui-logo.yaml create mode 100644 docs/content/examples/custom-admin-ui-navigation.yaml create mode 100644 docs/content/examples/custom-admin-ui-pages.yaml create mode 100644 docs/content/examples/custom-admin-ui.yaml create mode 100644 docs/content/examples/custom-field-type.yaml create mode 100644 docs/content/examples/custom-field-view.yaml create mode 100644 docs/content/examples/default-values.yaml create mode 100644 docs/content/examples/document-field-customisation.yaml create mode 100644 docs/content/examples/document-field.yaml create mode 100644 docs/content/examples/extend-express-app.yaml create mode 100644 docs/content/examples/extend-graphql-schema-nexus.yaml create mode 100644 docs/content/examples/extend-graphql-schema-ts.yaml create mode 100644 docs/content/examples/extend-graphql-schema.yaml create mode 100644 docs/content/examples/heroku.yaml create mode 100644 docs/content/examples/json-field.yaml create mode 100644 docs/content/examples/next-js.yaml create mode 100644 docs/content/examples/railway.yaml create mode 100644 docs/content/examples/singleton.yaml create mode 100644 docs/content/examples/task-manager.yaml create mode 100644 docs/content/examples/testing.yaml create mode 100644 docs/content/examples/virtual-field.yaml create mode 100644 docs/content/featured-docs.yaml create mode 100644 docs/content/featured-examples.yaml create mode 100644 docs/content/navigation.yaml create mode 100644 docs/keystatic/get-featured-docs-map.ts create mode 100644 docs/keystatic/get-navigation-map.ts create mode 100644 docs/keystatic/gradient-selector.ts create mode 100644 docs/keystatic/keystatic-reader.ts diff --git a/docs/app/(site)/blog/page.tsx b/docs/app/(site)/blog/page.tsx index d28c66bd221..206221afd51 100644 --- a/docs/app/(site)/blog/page.tsx +++ b/docs/app/(site)/blog/page.tsx @@ -11,8 +11,8 @@ export const metadata: Metadata = { title: 'Keystone Blog', description: 'Blog posts from the team maintaining Keystone.', openGraph: { - images: `${siteBaseUrl}/assets/blog/the-keystone-blog-cover.png` - } + images: `${siteBaseUrl}/assets/blog/the-keystone-blog-cover.png`, + }, } export default async function Docs () { diff --git a/docs/app/(site)/docs/[...rest]/page-client.tsx b/docs/app/(site)/docs/[...rest]/page-client.tsx index 1f8b251ec0d..c5cfe1b1b87 100644 --- a/docs/app/(site)/docs/[...rest]/page-client.tsx +++ b/docs/app/(site)/docs/[...rest]/page-client.tsx @@ -5,7 +5,7 @@ import { useParams } from 'next/navigation' import { type Document } from './page' import { extractHeadings, Markdoc } from '../../../../components/Markdoc' -import { DocsPage } from '../../../../components/Page' + import { Heading } from '../../../../components/docs/Heading' export default function PageClient ({ document }: { document: Document }) { @@ -17,10 +17,7 @@ export default function PageClient ({ document }: { document: Document }) { ] return ( - + <> {document.title} @@ -28,6 +25,6 @@ export default function PageClient ({ document }: { document: Document }) { {document.content.children.map((child, i) => ( ))} - + ) } diff --git a/docs/app/(site)/docs/[...rest]/page.tsx b/docs/app/(site)/docs/[...rest]/page.tsx index ea5efb39653..c9ce256984e 100644 --- a/docs/app/(site)/docs/[...rest]/page.tsx +++ b/docs/app/(site)/docs/[...rest]/page.tsx @@ -6,6 +6,8 @@ import { baseMarkdocConfig } from '../../../../markdoc/config' import PageClient from './page-client' import { type EntryWithResolvedLinkedFiles } from '@keystatic/core/reader' import type keystaticConfig from '../../../../keystatic.config' +import { DocsLayout } from '../../../../components/docs/DocsLayout' +import { extractHeadings } from '../../../../components/Markdoc' export type Document = NonNullable< Pick< @@ -22,16 +24,20 @@ export default async function DocPage ({ params }) { }) if (!doc) return notFound() + const transformedDoc: Document = { + ...doc, + content: transform(doc.content.node, baseMarkdocConfig) as Tag, + } + + const headings = [ + { id: 'title', depth: 1, label: transformedDoc.title }, + // ...extractHeadings(transformedDoc.content), + ] + return ( - + + + ) } diff --git a/docs/app/(site)/docs/config/overview/page-client.tsx b/docs/app/(site)/docs/config/overview/page-client.tsx index d4856220e3a..293146cdd57 100644 --- a/docs/app/(site)/docs/config/overview/page-client.tsx +++ b/docs/app/(site)/docs/config/overview/page-client.tsx @@ -5,19 +5,13 @@ import { CommunitySlackCTA } from '../../../../../components/docs/CommunitySlackCTA' import { Type } from '../../../../../components/primitives/Type' import { Well } from '../../../../../components/primitives/Well' -import { DocsPage } from '../../../../../components/Page' import { useMediaQuery } from '../../../../../lib/media' export default function Docs () { const mq = useMediaQuery() return ( - + <> Configuration Overview @@ -60,6 +54,6 @@ export default function Docs () { and one-time authentication tokens. - + ) } diff --git a/docs/app/(site)/docs/config/overview/page.tsx b/docs/app/(site)/docs/config/overview/page.tsx index 0f759646ce3..fe67804f1c5 100644 --- a/docs/app/(site)/docs/config/overview/page.tsx +++ b/docs/app/(site)/docs/config/overview/page.tsx @@ -1,3 +1,4 @@ +import { DocsLayout } from '../../../../../components/docs/DocsLayout' import PageClient from './page-client' export const metadata = { @@ -6,5 +7,9 @@ export const metadata = { } export default function Docs () { - return + return ( + + + + ) } diff --git a/docs/app/(site)/docs/examples/page-client.tsx b/docs/app/(site)/docs/examples/page-client.tsx index 0a9b1b01069..965e3d8ad56 100644 --- a/docs/app/(site)/docs/examples/page-client.tsx +++ b/docs/app/(site)/docs/examples/page-client.tsx @@ -4,7 +4,6 @@ import { GitHubExamplesCTA } from '../../../../components/docs/GitHubExamplesCTA' import { Type } from '../../../../components/primitives/Type' -import { DocsPage } from '../../../../components/Page' import { Well } from '../../../../components/primitives/Well' import { useMediaQuery } from '../../../../lib/media' import { InlineCode } from '../../../../components/primitives/Code' @@ -13,11 +12,7 @@ export default function Docs () { const mq = useMediaQuery() return ( - + <> Examples @@ -307,6 +302,6 @@ export default function Docs () { project. One-click deployment included. - + ) } diff --git a/docs/app/(site)/docs/examples/page.tsx b/docs/app/(site)/docs/examples/page.tsx index b0b2f013c50..f85a7046958 100644 --- a/docs/app/(site)/docs/examples/page.tsx +++ b/docs/app/(site)/docs/examples/page.tsx @@ -1,3 +1,4 @@ +import { DocsLayout } from '../../../../components/docs/DocsLayout' import PageClient from './page-client' export const metadata = { @@ -7,5 +8,9 @@ export const metadata = { } export default function Docs () { - return + return ( + + + + ) } diff --git a/docs/app/(site)/docs/guides/document-field-demo/page-client.tsx b/docs/app/(site)/docs/guides/document-field-demo/page-client.tsx index cfa23dea49d..a4069d16453 100644 --- a/docs/app/(site)/docs/guides/document-field-demo/page-client.tsx +++ b/docs/app/(site)/docs/guides/document-field-demo/page-client.tsx @@ -4,7 +4,7 @@ import React from 'react' import { H1, H2 } from '../../../../../components/docs/Heading' -import { DocsPage } from '../../../../../components/Page' + import { DocumentEditorDemo, DocumentFeaturesProvider, @@ -16,27 +16,7 @@ import { InlineCode } from '../../../../../components/primitives/Code' export default function DocumentFieldDemo () { const title = 'Document Fields Demo' return ( - + <>

{title}

@@ -85,6 +65,6 @@ export default function DocumentFieldDemo () {
-
+ ) } diff --git a/docs/app/(site)/docs/guides/document-field-demo/page.tsx b/docs/app/(site)/docs/guides/document-field-demo/page.tsx index 012d03fcb5c..5d1bc3639bc 100644 --- a/docs/app/(site)/docs/guides/document-field-demo/page.tsx +++ b/docs/app/(site)/docs/guides/document-field-demo/page.tsx @@ -1,3 +1,4 @@ +import { DocsLayout } from '../../../../../components/docs/DocsLayout' import PageClient from './page-client' export const metadata = { @@ -7,5 +8,29 @@ export const metadata = { } export default function DocumentFieldDemo () { - return + return ( + + + + ) } diff --git a/docs/app/(site)/docs/guides/overview/page-client.tsx b/docs/app/(site)/docs/guides/overview/page-client.tsx index e4e5ba8814a..fff1a218e37 100644 --- a/docs/app/(site)/docs/guides/overview/page-client.tsx +++ b/docs/app/(site)/docs/guides/overview/page-client.tsx @@ -5,19 +5,13 @@ import { CommunitySlackCTA } from '../../../../../components/docs/CommunitySlackCTA' import { Type } from '../../../../../components/primitives/Type' import { Well } from '../../../../../components/primitives/Well' -import { DocsPage } from '../../../../../components/Page' import { useMediaQuery } from '../../../../../lib/media' export default function Docs () { const mq = useMediaQuery() return ( - + <> Keystone Guides @@ -109,6 +103,6 @@ export default function Docs () { Learn how to add your own custom pages to Keystone’s Admin UI. - + ) } diff --git a/docs/app/(site)/docs/guides/overview/page.tsx b/docs/app/(site)/docs/guides/overview/page.tsx index 091b7dfb605..fc8a08ad772 100644 --- a/docs/app/(site)/docs/guides/overview/page.tsx +++ b/docs/app/(site)/docs/guides/overview/page.tsx @@ -1,3 +1,4 @@ +import { DocsLayout } from '../../../../../components/docs/DocsLayout' import PageClient from './page-client' export const metadata = { @@ -7,5 +8,9 @@ export const metadata = { } export default function Docs () { - return + return ( + + + + ) } diff --git a/docs/app/(site)/docs/page-client.tsx b/docs/app/(site)/docs/page-client.tsx index 3149876e56c..48ffe26f1a2 100644 --- a/docs/app/(site)/docs/page-client.tsx +++ b/docs/app/(site)/docs/page-client.tsx @@ -2,35 +2,19 @@ 'use client' -import Link from 'next/link' - import { CommunitySlackCTA } from '../../../components/docs/CommunitySlackCTA' import { Keystone5DocsCTA } from '../../../components/docs/Keystone5DocsCTA' -import { Examples } from '../../../components/docs/ExamplesList' -import { Walkthroughs } from '../../../components/docs/WalkthroughsList' import { Type } from '../../../components/primitives/Type' -import { Well } from '../../../components/primitives/Well' -import { DocsPage } from '../../../components/Page' -import { useMediaQuery } from '../../../lib/media' import { CommunityCta } from '../../../components/content/CommunityCta' -import { Content } from '../../../components/icons/Content' -import { Code } from '../../../components/icons/Code' -import { Bulb } from '../../../components/icons/Bulb' -import { Video } from '../../../components/icons/Video' -import { Organization } from '../../../components/icons/Organization' import { Alert } from '../../../components/primitives/Alert' import { Button } from '../../../components/primitives/Button' import { ArrowR } from '../../../components/icons' +import { KeystoneExperience } from '../../../components/docs/KeystoneExperience' -export default function Docs () { - const mq = useMediaQuery() - +export default function DocsPageClient ({ featuredExamples, featuredDocs }) { + return ( - + <> Developer Docs @@ -50,294 +34,10 @@ export default function Docs () { Learn more - - - The Keystone Experience - -
- - Discover the vision behind Keystone and what it's like to work with. If you’ve just heard - of Keystone, start here first: - -
a': { - borderRadius: '1rem', - boxShadow: '0 0 5px var(--shadow)', - padding: '1.5rem', - color: 'var(--app-bg)', - transition: 'box-shadow 0.2s ease, transform 0.2s ease, padding 0.2s ease', - textDecoration: 'none !important', - '&:hover, &:focus': { - boxShadow: '0 7px 21px var(--shadow)', - transform: 'translateY(-4px)', - }, - '& svg': { - height: '2rem', - }, - }, - })} - > - - - - - - Why Keystone → - - - The makers. The vision. What’s in the box, and what you can build with it. - - -
-
-
a': { - borderRadius: '1rem', - boxShadow: '0 0 5px var(--shadow)', - padding: '1.5rem', - color: 'var(--app-bg)', - transition: 'box-shadow 0.2s ease, transform 0.2s ease, padding 0.2s ease', - textDecoration: 'none !important', - '&:hover, &:focus': { - boxShadow: '0 7px 21px var(--shadow)', - transform: 'translateY(-4px)', - }, - '& svg': { - height: '2rem', - }, - }, - })} - > - - - - For Developers → - - - Built the way you’d want it made. Keystone fits with the tools you know and love. - - - - - - For Editors → - - - The configurable editing environment you need to do your best work. - - - - - - For Organisations → - - - Own your data. Start fast. Find your audience anywhere. Scale on your terms. - - -
- - - Walkthroughs - - - - Step-by-step instructions for getting things done with Keystone. - - - - - - Guides - - - - Practical explanations of Keystone’s fundamental building blocks. When you’re trying to get - something done, Keystone guides show you how to think about, and get the most out of each - feature. - - -
- - Keystone’s CLI helps you develop, build, and deploy projects. This guide explains all you - need to standup a new backend in the terminal. - - - Learn how to reason about and configure relationships in Keystone, so you can bring value - to your project through structured content. - - - Query filters are an integral part of Keystone’s powerful GraphQL APIs. This guide will - show you how to use filters to get the data you need from your system. - - - Learn how to use Hooks within your schema to extend Keystone’s powerful CRUD GraphQL APIs - with your own business logic. - - - Keystone’s document field is a highly customisable rich text editor that stores content as - structured JSON. Learn how to configure it and incorporate your own custom React - components. - - - Test drive the many features of Keystone’s Document field on this website. - - - Learn how to define your own custom field types in Keystone, with customisable backend - data structure, and Admin UI appearance. - - - Learn how to test the behaviour of your Keystone system to ensure it does what you expect. - - - Virtual fields offer a powerful way to extend your GraphQL API. This guide introduces the - syntax and shows you how start simply and end up with a complex result. - - - Keystone supports Postgres, MySQL and SQLite. This guide explains how to choose the best - for your project. - - - Learn how to store and manage Images and Files in Keystone. - -
- - - Example projects - - - - A growing collection of projects you can run locally to learn more about Keystone features. - Use these as a reference for best practice, and a jumping off point when adding features to - your own Keystone project.{' '} - - View on Github → - - - - - + + {featuredDocs} + {featuredExamples} -
+ ) } diff --git a/docs/app/(site)/docs/page.tsx b/docs/app/(site)/docs/page.tsx index 9d8f8d4e375..a81481a282a 100644 --- a/docs/app/(site)/docs/page.tsx +++ b/docs/app/(site)/docs/page.tsx @@ -1,4 +1,8 @@ import PageClient from './page-client' +import { FeaturedExamples } from '../../../components/docs/featured-examples' +import { FeaturedDocs } from '../../../components/docs/featured-docs' + +import { DocsLayout } from '../../../components/docs/DocsLayout' export const metadata = { title: 'Keystone Docs Home', @@ -6,5 +10,12 @@ export const metadata = { } export default function Docs () { - return + return ( + + } + featuredDocs={} + /> + + ) } diff --git a/docs/app/(site)/docs/walkthroughs/page-client.tsx b/docs/app/(site)/docs/walkthroughs/page-client.tsx index 1d7da3594de..02bbd275cdc 100644 --- a/docs/app/(site)/docs/walkthroughs/page-client.tsx +++ b/docs/app/(site)/docs/walkthroughs/page-client.tsx @@ -3,7 +3,6 @@ 'use client' import { Type } from '../../../../components/primitives/Type' -import { DocsPage } from '../../../../components/Page' import { Well } from '../../../../components/primitives/Well' import { useMediaQuery } from '../../../../lib/media' import { InlineCode } from '../../../../components/primitives/Code' @@ -71,12 +70,7 @@ export function Foundations () { export default function Docs () { return ( - + <> Walkthroughs @@ -106,6 +100,6 @@ export default function Docs () { - + ) } diff --git a/docs/app/(site)/docs/walkthroughs/page.tsx b/docs/app/(site)/docs/walkthroughs/page.tsx index 291536e6097..42ca2e65c3a 100644 --- a/docs/app/(site)/docs/walkthroughs/page.tsx +++ b/docs/app/(site)/docs/walkthroughs/page.tsx @@ -1,3 +1,4 @@ +import { DocsLayout } from '../../../../components/docs/DocsLayout' import PageClient from './page-client' export const metadata = { @@ -7,5 +8,9 @@ export const metadata = { } export default function Docs () { - return + return ( + + + + ) } diff --git a/docs/app/(site)/updates/page-client.tsx b/docs/app/(site)/updates/page-client.tsx index c1bb190ce9d..bc9ae23c10a 100644 --- a/docs/app/(site)/updates/page-client.tsx +++ b/docs/app/(site)/updates/page-client.tsx @@ -9,7 +9,6 @@ import { InlineCode } from '../../../components/primitives/Code' import { Button } from '../../../components/primitives/Button' import { Alert } from '../../../components/primitives/Alert' import { Type } from '../../../components/primitives/Type' -import { DocsPage } from '../../../components/Page' import { ArrowR } from '../../../components/icons/ArrowR' import { Emoji } from '../../../components/primitives/Emoji' import { useMediaQuery } from '../../../lib/media' @@ -126,11 +125,7 @@ export default function WhatsNew () { const mq = useMediaQuery() return ( - + <> Latest News @@ -912,6 +907,6 @@ export default function WhatsNew () { Community Slack - + ) } diff --git a/docs/app/(site)/updates/page.tsx b/docs/app/(site)/updates/page.tsx index f8206f12bd5..3a8f7b0a1de 100644 --- a/docs/app/(site)/updates/page.tsx +++ b/docs/app/(site)/updates/page.tsx @@ -1,10 +1,15 @@ +import { DocsLayout } from '../../../components/docs/DocsLayout' import PageClient from './page-client' export const metadata = { - title:'Latest News', - description:'What’s new with Keystone. A snapshot of announcements and recent releases.' + title: 'Latest News', + description: 'What’s new with Keystone. A snapshot of announcements and recent releases.', } export default function WhatsNew () { - return + return ( + + + + ) } diff --git a/docs/app/(site)/updates/roadmap/page-client.tsx b/docs/app/(site)/updates/roadmap/page-client.tsx index 3068f34a016..b569490da7d 100644 --- a/docs/app/(site)/updates/roadmap/page-client.tsx +++ b/docs/app/(site)/updates/roadmap/page-client.tsx @@ -11,7 +11,7 @@ import { Gradient } from '../../../../components/primitives/Gradient' import { Alert } from '../../../../components/primitives/Alert' import { Emoji } from '../../../../components/primitives/Emoji' import { Type } from '../../../../components/primitives/Type' -import { DocsPage } from '../../../../components/Page' + import { useMediaQuery } from '../../../../lib/media' function TimelineItem ({ children }: { children: ReactNode }) { @@ -194,10 +194,7 @@ export default function Roadmap () { const mq = useMediaQuery() return ( - + <> Roadmap @@ -484,6 +481,6 @@ export default function Roadmap () { . - + ) } diff --git a/docs/app/(site)/updates/roadmap/page.tsx b/docs/app/(site)/updates/roadmap/page.tsx index 302059d2cea..945b8443e4a 100644 --- a/docs/app/(site)/updates/roadmap/page.tsx +++ b/docs/app/(site)/updates/roadmap/page.tsx @@ -1,3 +1,4 @@ +import { DocsLayout } from '../../../../components/docs/DocsLayout' import PageClient from './page-client' export const metadata = { @@ -6,5 +7,9 @@ export const metadata = { } export default function Roadmap () { - return + return ( + + + + ) } diff --git a/docs/components/Page.tsx b/docs/components/Page.tsx index eab9c583a8c..ec2863db525 100644 --- a/docs/components/Page.tsx +++ b/docs/components/Page.tsx @@ -10,100 +10,11 @@ import { TableOfContents } from './docs/TableOfContents' import { Wrapper } from './primitives/Wrapper' import { EditButton } from './primitives/EditButton' import { Breadcrumbs } from './Breadcrumbs' -import { Sidebar } from './docs/Sidebar' import { Stack } from './primitives/Stack' import { Header } from './Header' -import { Footer, DocsFooter } from './Footer' +import { Footer } from './Footer' import { type HeadingType } from './Markdoc' -const pagesWithUpdatesSidebar = ['/updates'] - -export function DocsPage ({ - children, - headings = [], - noProse, - noRightNav, - isIndexPage, - editPath, -}: { - children: ReactNode - headings?: HeadingType[] - noProse?: boolean - noRightNav?: boolean - isIndexPage?: boolean - editPath?: string -}) { - const contentRef = useRef(null) - const mq = useMediaQuery() - const pathname = usePathname() - const isUpdatesPage = pagesWithUpdatesSidebar.some((p) => pathname?.startsWith(p)) - - return ( -
-
- - -
-
- - - - - -
- {children} -
-
- {!!headings.length && !noRightNav && ( - - )} -
- -
-
- ) -} - export function BlogPage ({ children, headings = [], diff --git a/docs/components/docs/DocsFooter.tsx b/docs/components/docs/DocsFooter.tsx deleted file mode 100644 index 1e5d67ff515..00000000000 --- a/docs/components/docs/DocsFooter.tsx +++ /dev/null @@ -1,23 +0,0 @@ - -/** @jsxImportSource @emotion/react */ - -import { Wrapper } from '../primitives/Wrapper' -import { Emoji } from '../primitives/Emoji' - -export function DocsFooter () { - return ( -
- - Made in by Thinkmill. Supported with{' '} - by the awesome Keystone community. - -
- ) -} diff --git a/docs/components/docs/DocsLayout.tsx b/docs/components/docs/DocsLayout.tsx new file mode 100644 index 00000000000..127d57a6720 --- /dev/null +++ b/docs/components/docs/DocsLayout.tsx @@ -0,0 +1,6 @@ +import { DocsLayoutClient } from './DocsLayoutClient' +import { DocsNavigation } from './docs-navigation' + +export async function DocsLayout (props) { + return } /> +} diff --git a/docs/components/docs/DocsLayoutClient.tsx b/docs/components/docs/DocsLayoutClient.tsx new file mode 100644 index 00000000000..e7ecc72f47f --- /dev/null +++ b/docs/components/docs/DocsLayoutClient.tsx @@ -0,0 +1,109 @@ +/** @jsxImportSource @emotion/react */ + +'use client' + +import { type ReactNode, useRef } from 'react' +import { type HeadingType } from '../Markdoc' +import { usePathname } from 'next/navigation' +import { Header } from '../Header' +import { Wrapper } from '../primitives/Wrapper' +import { Sidebar } from './Sidebar' +import { Stack } from '../primitives/Stack' +import { Breadcrumbs } from '../Breadcrumbs' +import { EditButton } from '../primitives/EditButton' +import { TableOfContents } from './TableOfContents' + +import { useMediaQuery } from '../../lib/media' +import { DocsFooter } from '../Footer' + +const pagesWithUpdatesSidebar = ['/updates'] + +export function DocsLayoutClient ({ + children, + headings = [], + noProse, + noRightNav, + isIndexPage, + editPath, + docsNavigation +}: { + children: ReactNode + headings?: HeadingType[] + noProse?: boolean + noRightNav?: boolean + isIndexPage?: boolean + editPath?: string + docsNavigation: ReactNode +}) { + const contentRef = useRef(null) + const mq = useMediaQuery() + const pathname = usePathname() + + + const isUpdatesPage = pagesWithUpdatesSidebar.some((p) => pathname?.startsWith(p)) + + return ( +
+
+ + +
+
+ + + + + +
+ {children} +
+
+ {!!headings.length && !noRightNav && ( + + )} +
+ +
+
+ ) +} \ No newline at end of file diff --git a/docs/components/docs/ExamplesList.tsx b/docs/components/docs/ExamplesList.tsx deleted file mode 100644 index 182777da85e..00000000000 --- a/docs/components/docs/ExamplesList.tsx +++ /dev/null @@ -1,159 +0,0 @@ - -/** @jsxImportSource @emotion/react */ - -import { Well } from '../primitives/Well' -import { useMediaQuery } from '../../lib/media' -import { InlineCode } from '../../components/primitives/Code' - -export function Examples () { - const mq = useMediaQuery() - return ( -
- - A basic Blog schema with Posts and Authors. Use this as a starting place for learning how to - use Keystone. It’s also a starter for other feature projects. - - - A basic Task Management app, with Tasks and People who can be assigned to tasks. Great for - learning how to use Keystone. It’s also a starter for other feature projects. - - - Shows you how to extend the Keystone GraphQL API with custom queries and mutations. Builds - upon the Blog starter project. - - - Demonstrates how to use default values for fields. Builds upon the Task Manager starter - project. - - - Implements virtual fields in a Keystone list. Builds on the Blog starter project. - - - Illustrates how to configure document fields in your Keystone - system and render their data in a frontend application. Builds on the Blog starter project. - - - Shows you how to write tests against the GraphQL API to your Keystone system. Builds on the - Authentication example project. - - - Adds password-based authentication to the Task Manager starter project. - - - Illustrates how to use the json field type. - - - Adds a custom Admin UI view to a json field which provides a - customised editing experience for users. - - - Adds a custom field type based on the integer field type which lets - users rate items on a 5-star scale. - - - Adds a custom page in the Admin UI. - - - Adds a custom logo component in the Admin UI. - - - Adds a custom Navigation component to the Admin UI. - - - Example to demonstrate customisation of Keystone's document field and document renderer. - -
- ) -} diff --git a/docs/components/docs/KeystoneExperience.tsx b/docs/components/docs/KeystoneExperience.tsx new file mode 100644 index 00000000000..3d49c8dc214 --- /dev/null +++ b/docs/components/docs/KeystoneExperience.tsx @@ -0,0 +1,217 @@ +/** @jsxImportSource @emotion/react */ + +'use client' + +import Link from 'next/link' + +import { Bulb } from '../icons/Bulb' +import { Content } from '../icons/Content' +import { Code } from '../icons/Code' +import { Organization } from '../icons/Organization' +import { Video } from '../icons/Video' + +import { useMediaQuery } from '../../lib/media' +import { Type } from '../primitives/Type' + +export function KeystoneExperience () { + const mq = useMediaQuery() + return ( + <> + + The Keystone Experience + +
+ + Discover the vision behind Keystone and what it's like to work with. If you’ve just heard + of Keystone, start here first: + +
a': { + borderRadius: '1rem', + boxShadow: '0 0 5px var(--shadow)', + padding: '1.5rem', + color: 'var(--app-bg)', + transition: 'box-shadow 0.2s ease, transform 0.2s ease, padding 0.2s ease', + textDecoration: 'none !important', + '&:hover, &:focus': { + boxShadow: '0 7px 21px var(--shadow)', + transform: 'translateY(-4px)', + }, + '& svg': { + height: '2rem', + }, + }, + })} + > + + + + + + Why Keystone → + + + The makers. The vision. What’s in the box, and what you can build with it. + + +
+
+
a': { + borderRadius: '1rem', + boxShadow: '0 0 5px var(--shadow)', + padding: '1.5rem', + color: 'var(--app-bg)', + transition: 'box-shadow 0.2s ease, transform 0.2s ease, padding 0.2s ease', + textDecoration: 'none !important', + '&:hover, &:focus': { + boxShadow: '0 7px 21px var(--shadow)', + transform: 'translateY(-4px)', + }, + '& svg': { + height: '2rem', + }, + }, + })} + > + + + + For Developers → + + + Built the way you’d want it made. Keystone fits with the tools you know and love. + + + + + + For Editors → + + + The configurable editing environment you need to do your best work. + + + + + + For Organisations → + + + Own your data. Start fast. Find your audience anywhere. Scale on your terms. + + +
+ + ) +} diff --git a/docs/components/docs/Navigation.tsx b/docs/components/docs/Navigation.tsx index d0c44f1d472..22255d35817 100644 --- a/docs/components/docs/Navigation.tsx +++ b/docs/components/docs/Navigation.tsx @@ -67,7 +67,7 @@ type NavSectionProps = { children: ReactNode } -function NavSection ({ title, children }: NavSectionProps) { +export function NavSection ({ title, children }: NavSectionProps) { const { isSectionCollapsed, collapseSection, expandSection } = useNavContext() const isCollapsed = isSectionCollapsed(title) return ( @@ -190,111 +190,10 @@ export function PrimaryNavItem ({ href, children }: PrimaryNavItemProps) { ) } -export function DocsNavigation () { +export function DocsNavigation ({ docsNavigation }: { docsNavigation?: React.ReactNode }) { return ( - + {docsNavigation} ) } diff --git a/docs/components/docs/Sidebar.tsx b/docs/components/docs/Sidebar.tsx index c139b9b2980..978c051edcb 100644 --- a/docs/components/docs/Sidebar.tsx +++ b/docs/components/docs/Sidebar.tsx @@ -5,9 +5,10 @@ import { DocsNavigation, UpdatesNavigation } from './Navigation' type SidebarProps = { isUpdatesPage?: boolean + docsNavigation?: React.ReactNode } -export function Sidebar ({ isUpdatesPage }: SidebarProps) { +export function Sidebar ({ isUpdatesPage, docsNavigation }: SidebarProps) { const mq = useMediaQuery() const Navigation = isUpdatesPage ? UpdatesNavigation : DocsNavigation @@ -40,7 +41,7 @@ export function Sidebar ({ isUpdatesPage }: SidebarProps) { paddingBottom: '2rem', }} > - + diff --git a/docs/components/docs/WalkthroughsList.tsx b/docs/components/docs/WalkthroughsList.tsx deleted file mode 100644 index 7d013ae48b3..00000000000 --- a/docs/components/docs/WalkthroughsList.tsx +++ /dev/null @@ -1,59 +0,0 @@ - -/** @jsxImportSource @emotion/react */ - -import { Well } from '../primitives/Well' -import { useMediaQuery } from '../../lib/media' -import { InlineCode } from '../../components/primitives/Code' - -export function Walkthroughs () { - const mq = useMediaQuery() - return ( -
-
- - Take a tour of Keystone in minutes with our CLI starter project - -
-
- - Get Keystone up and running with your first content type - - - Connect two content types and learn how to configure the appearance of field inputs - - - Support publishing needs with Keystone's select and{' '} - timestamp fields - - - Add sessions, password protection, and a sign-in screen to your Keystone app - - - Add a powerful document field to your app and learn how to - configure it to meet your needs - -
-
- ) -} diff --git a/docs/components/docs/docs-navigation/client.tsx b/docs/components/docs/docs-navigation/client.tsx new file mode 100644 index 00000000000..b9139fef6b4 --- /dev/null +++ b/docs/components/docs/docs-navigation/client.tsx @@ -0,0 +1,48 @@ +/** @jsxImportSource @emotion/react */ + +'use client' + +import { type NavigationMap } from '.' +import { Badge } from '../../primitives/Badge' +import { NavItem, NavSection } from '../Navigation' + +function KeystaticNavItem ({ item }: { item: NonNullable[number]['items'][number] }) { + return ( + + {item.label} + {/* Status badges */} + {item.status !== 'default' && ' '} + {(item.status === 'new' || item.status === 'updated') && ( + {item.status} + )} + + ) +} + +export function DocsNavigationClient ({ navigationMap }: { navigationMap: NavigationMap }) { + if (!navigationMap) return null + + return ( + + ) +} diff --git a/docs/components/docs/docs-navigation/index.tsx b/docs/components/docs/docs-navigation/index.tsx new file mode 100644 index 00000000000..fe542a17521 --- /dev/null +++ b/docs/components/docs/docs-navigation/index.tsx @@ -0,0 +1,33 @@ +import { reader } from '../../../lib/keystatic-reader' +import { DocsNavigationClient } from './client' + +export type NavigationMap = Awaited> + +export async function getNavigationMap () { + const navigation = await reader.singletons.navigation.read() + const pages = await reader.collections.docs.all() + + const pagesBySlug = Object.fromEntries(pages.map((page) => [page.slug, page])) + + const navigationMap = navigation?.navGroups.map(({ groupName, items }) => ({ + groupName, + items: items.map(({ label, link, status }) => { + const { discriminant, value } = link + const page = discriminant === 'page' && value ? pagesBySlug[value] : null + const url = discriminant === 'url' ? value : `/docs/${page?.slug}` + + return { + label: label || page?.entry.title || '', + href: url || '', + status, + } + }), + })) + + return navigationMap +} + +export async function DocsNavigation () { + const navigationMap = await getNavigationMap() + return +} \ No newline at end of file diff --git a/docs/components/docs/featured-docs/client.tsx b/docs/components/docs/featured-docs/client.tsx new file mode 100644 index 00000000000..d11f84401ee --- /dev/null +++ b/docs/components/docs/featured-docs/client.tsx @@ -0,0 +1,112 @@ +/** @jsxImportSource @emotion/react */ + +'use client' + +import { Well } from '../../primitives/Well' +import { useMediaQuery } from '../../../lib/media' +import { Type } from '../../primitives/Type' +import { type FeaturedDocsMap } from '.' +import { Markdoc } from '../../Markdoc' +import { useId } from 'react' + +export function FeaturedDocsClient ({ featuredDocsMap }: { featuredDocsMap: FeaturedDocsMap }) { + if (!featuredDocsMap) return null + + // Separating the first group/item for featured UI treatment + const [firstGroup, ...restGroups] = featuredDocsMap + const [featuredItem, ...restItems] = firstGroup.items + + return ( + <> + {/* First Group */} + + {firstGroup.groupName} + + + {firstGroup.groupDescription.children.map((child, i) => ( + + ))} + + + {/* Featured Item */} + {!!featuredItem.description && ( + + + {featuredItem.description.children.map((child, i) => ( + + ))} + + + )} + {/* Remaining items of the first group */} + + {restItems.map((item, i) => ( + + ))} + + + {/* Remaining groups */} + {!!restGroups && + restGroups.map((group, i) => ( +
+ + {group.groupName} + + + {group.groupDescription.children.map((child, j) => ( + + ))} + + + + {group.items.map((item, j) => ( + + ))} + +
+ ))} + + ) +} + +function FullWidthContainer ({ children }: { children: React.ReactNode }) { + const mq = useMediaQuery() + return ( +
+ {children} +
+ ) +} + +function SplitContainer ({ children }: { children: React.ReactNode }) { + const mq = useMediaQuery() + return ( +
+ {children} +
+ ) +} + +function DocWell ({ group, item }) { + const id = useId() + return ( + + {item.description?.children.map((child, i) => ( + + ))} + + ) +} diff --git a/docs/components/docs/featured-docs/index.tsx b/docs/components/docs/featured-docs/index.tsx new file mode 100644 index 00000000000..d958d623bd2 --- /dev/null +++ b/docs/components/docs/featured-docs/index.tsx @@ -0,0 +1,64 @@ +import { type Tag, transform } from '@markdoc/markdoc' +import { reader } from '../../../lib/keystatic-reader' +import { FeaturedDocsClient } from './client' +import { baseMarkdocConfig } from '../../../markdoc/config' + +export type FeaturedDocsMap = Awaited> + +async function getFeaturedDocsMap () { + const featuredDocs = await reader.singletons.featuredDocs.read({ resolveLinkedFiles: true }) + if (!featuredDocs) return null + + // We need the docs data as well... + const docs = await reader.collections.docs.all() + // Individual doc accessor + const docsBySlug = new Map(docs.map((doc) => [doc.slug, doc])) + + // Each `item` will need to be processed differently based on the `link` discriminant + async function processItem ({ label, link, wide, gradient }) { + const { discriminant, value } = link + let description: Tag | null = null + let href: string = '#' + + switch (discriminant) { + case 'url': { + description = transform(value.description.node, baseMarkdocConfig) as Tag + href = value.url + break + } + case 'docs': { + const docPage = value.docPage ? docsBySlug.get(value.docPage) : null + if (!docPage) throw new Error(`No doc page found for slug: ${value.docPage}`) + description = transform(value.description.node, baseMarkdocConfig) as Tag + href = docPage.slug ? `/docs/${docPage.slug}` : '#' + break + } + } + + return { label, description, href, wide, gradient } + } + + // Processing all groups... + const processedGroups = await Promise.all( + featuredDocs.groups.map(async ({ groupName, groupDescription, gradient, items }) => { + const transformedGroupDescription = transform(groupDescription.node, baseMarkdocConfig) as Tag + const processedItems = await Promise.all(items.map(processItem)) + + return { + groupName, + groupDescription: transformedGroupDescription, + gradient, + items: processedItems, + } + }) + ) + + return processedGroups +} + +export async function FeaturedDocs () { + const featuredDocsMap = await getFeaturedDocsMap() + return +} + + diff --git a/docs/components/docs/featured-examples/client.tsx b/docs/components/docs/featured-examples/client.tsx new file mode 100644 index 00000000000..d6ca864b9fa --- /dev/null +++ b/docs/components/docs/featured-examples/client.tsx @@ -0,0 +1,55 @@ +/** @jsxImportSource @emotion/react */ + +'use client' + +import { Well } from '../../primitives/Well' +import { useMediaQuery } from '../../../lib/media' +import { Markdoc } from '../../Markdoc' +import { type FeaturedExamples } from '.' +import { Type } from '../../primitives/Type' + +export default function ExamplesList ({ featuredExamples }: { featuredExamples: FeaturedExamples }) { + if (!featuredExamples) return null + const mq = useMediaQuery() + return ( + <> + + {featuredExamples.label} + + + {!!featuredExamples.description && ( + + {featuredExamples.description.children.map((child, i) => ( + + ))} + + )} + +
+ {featuredExamples.items.map( + (item, i) => + !!item && ( + + {item.description.children.map((child, i) => ( + + ))} + + ) + )} +
+ + ) +} diff --git a/docs/components/docs/featured-examples/index.tsx b/docs/components/docs/featured-examples/index.tsx new file mode 100644 index 00000000000..807c350a10a --- /dev/null +++ b/docs/components/docs/featured-examples/index.tsx @@ -0,0 +1,41 @@ +import ClientComponent from './client' + +import { type Tag, transform } from '@markdoc/markdoc' +import { reader } from '../../../lib/keystatic-reader' +import { baseMarkdocConfig } from '../../../markdoc/config' + + +export type FeaturedExamples = Awaited> + +async function getFeaturedExamples () { + const featuredExamples = await reader.singletons.featuredExamples.read({ + resolveLinkedFiles: true, + }) + + if (!featuredExamples) return null + + // Get the rich text fields Markdoc-ready + const transformedFeaturedExamples = { + ...featuredExamples, + description: transform(featuredExamples.description.node, baseMarkdocConfig) as Tag, + items: await Promise.all( + featuredExamples.items.map(async (itemSlug) => { + const item = await reader.collections.examples.read(itemSlug, { + resolveLinkedFiles: true, + }) + if (!item) return null + return { + ...item, + description: transform(item.description.node, baseMarkdocConfig) as Tag, + } + }) + ), + } + + return transformedFeaturedExamples +} + +export async function FeaturedExamples () { + const featuredExamples = await getFeaturedExamples() + return +} diff --git a/docs/content/docs/config/config.md b/docs/content/docs/config/config.md index 308b61a83a4..86a98c712a3 100644 --- a/docs/content/docs/config/config.md +++ b/docs/content/docs/config/config.md @@ -166,7 +166,6 @@ export default config({ { mode: 'write', src: ` - import { jsx } from '@keystone-ui/core'; export default function Welcome() { return (

Welcome to my Keystone system

); diff --git a/docs/content/docs/graphql/filters.md b/docs/content/docs/graphql/filters.md index 90c726a1d2f..723a5a857ec 100644 --- a/docs/content/docs/graphql/filters.md +++ b/docs/content/docs/graphql/filters.md @@ -1,8 +1,10 @@ --- -title: "GraphQL Query Filters" -description: "A reference list of every filters available for every Keystone field type. Keystone filters are typically named after the field they are filtering." ---- +title: GraphQL Query Filters +description: >- + A reference list of every filters available for every Keystone field type. + Keystone filters are typically named after the field they are filtering. +--- Each field type provides its own set of filters which can be used with [queries](./overview#users). This page lists all the filters available for each field type. For more details on how to use filters in queries please consult to the [GraphQL Queries - Filters](../guides/filters) guide. @@ -11,36 +13,99 @@ For more details on how to use filters in queries please consult to the [GraphQL ### checkbox -| **Filter name** | **Type** | **Description** | -| --------------- | ----------------------- | ------------------------------- | -| `equals` | `Boolean` | Equals | -| `not` | `BooleanNullableFilter` | Does not match the inner filter | +{% table %} +- **Filter name** +- **Type** +- **Description** +--- +- `equals` +- `Boolean` +- Equals +--- +- `not` +- `BooleanNullableFilter` +- Does not match the inner filter +{% /table %} ### integer -| **Filter name** | **Type** | **Description** | -| --------------- | ------------------- | ------------------------------- | -| `equals` | `Int` | Equals | -| `lt` | `Int` | Less than | -| `lte` | `Int` | Less than or equal | -| `gt` | `Int` | Greater than | -| `gte` | `Int` | Greater than or equal | -| `in` | `[Int!]` | Is in the array | -| `notIn` | `[Int!]` | Is not in the array | -| `not` | `IntNullableFilter` | Does not match the inner filter | +{% table %} +- **Filter name** +- **Type** +- **Description** +--- +- `equals` +- `Int` +- Equals +--- +- `lt` +- `Int` +- Less than +--- +- `lte` +- `Int` +- Less than or equal +--- +- `gt` +- `Int` +- Greater than +--- +- `gte` +- `Int` +- Greater than or equal +--- +- `in` +- `[Int!]` +- Is in the array +--- +- `notIn` +- `[Int!]` +- Is not in the array +--- +- `not` +- `IntNullableFilter` +- Does not match the inner filter +{% /table %} ### bigInt -| **Filter name** | **Type** | **Description** | -| --------------- | ---------------------- | ------------------------------- | -| `equals` | `BigInt` | Equals | -| `lt` | `BigInt` | Less than | -| `lte` | `BigInt` | Less than or equal | -| `gt` | `BigInt` | Greater than | -| `gte` | `BigInt` | Greater than or equal | -| `in` | `[BigInt!]` | Is in the array | -| `notIn` | `[BigInt!]` | Is not in the array | -| `not` | `BigIntNullableFilter` | Does not match the inner filter | +{% table %} +- **Filter name** +- **Type** +- **Description** +--- +- `equals` +- `BigInt` +- Equals +--- +- `lt` +- `BigInt` +- Less than +--- +- `lte` +- `BigInt` +- Less than or equal +--- +- `gt` +- `BigInt` +- Greater than +--- +- `gte` +- `BigInt` +- Greater than or equal +--- +- `in` +- `[BigInt!]` +- Is in the array +--- +- `notIn` +- `[BigInt!]` +- Is not in the array +--- +- `not` +- `BigIntNullableFilter` +- Does not match the inner filter +{% /table %} ### json @@ -48,51 +113,136 @@ The `json` field type does not support filters. ### float -| **Filter name** | **Type** | **Description** | -| --------------- | --------------------- | ------------------------------- | -| `equals` | `Float` | Equals | -| `lt` | `Float` | Less than | -| `lte` | `Float` | Less than or equal | -| `gt` | `Float` | Greater than | -| `gte` | `Float` | Greater than or equal | -| `in` | `[Float!]` | Is in the array | -| `notIn` | `[Float!]` | Is not in the array | -| `not` | `FloatNullableFilter` | Does not match the inner filter | +{% table %} +- **Filter name** +- **Type** +- **Description** +--- +- `equals` +- `Float` +- Equals +--- +- `lt` +- `Float` +- Less than +--- +- `lte` +- `Float` +- Less than or equal +--- +- `gt` +- `Float` +- Greater than +--- +- `gte` +- `Float` +- Greater than or equal +--- +- `in` +- `[Float!]` +- Is in the array +--- +- `notIn` +- `[Float!]` +- Is not in the array +--- +- `not` +- `FloatNullableFilter` +- Does not match the inner filter +{% /table %} ### password -| **Filter name** | **Type** | **Description** | -| --------------- | --------- | --------------- | -| `isSet` | `Boolean` | A value is set | +{% table %} +- **Filter name** +- **Type** +- **Description** +--- +- `isSet` +- `Boolean` +- A value is set +{% /table %} ### select - If the `type` is `string`(the default), the same filters as `text` will be available. - If the `type` is `integer`, the same filters as `integer` will be available. - If the `type` is `enum`, the following filters will be available: - \| **Filter name** \| **Type** \| **Description** \| - \| --------------- \| ---------- \| ------------------- \| - \| `equals` \| `ListKeyFieldKeyType` | Equals | - \| `in` \| `[ListKeyFieldKeyType!]` | Is in the array | - \| `notIn` \| `[ListKeyFieldKeyType!]` | Is not in the array | - \| `not` \| `ListKeyFieldKeyTypeNullableFilter` | Does not match the inner filter | + | **Filter name** | **Type** | **Description** | + | --------------- | ---------- | ------------------- | + | `equals` | `ListKeyFieldKeyType` | Equals | + | `in` | `[ListKeyFieldKeyType!]` | Is in the array | + | `notIn` | `[ListKeyFieldKeyType!]` | Is not in the array | + | `not` | `ListKeyFieldKeyTypeNullableFilter` | Does not match the inner filter | ### text -| **Filter name** | **Type** | **Description** | **Notes** | -| --------------- | ---------------------------------------- | ----------------------------------------------------- | --------- | -| `equals` | `String` | Equals | | -| `lt` | `String` | Less than | | -| `lte` | `String` | Less than or equal | | -| `gt` | `String` | Greater than | | -| `gte` | `String` | Greater than or equal | | -| `contains` | `String` | Contains | [1] | -| `startsWith` | `String` | Starts with | [1] | -| `endsWith` | `String` | Ends with | [1] | -| `in` | `[String!]` | Is in the array | | -| `notIn` | `[String!]` | Is not in the array | | -| `mode` | `QueryMode` (`default` or `insensitive`) | Whether the filters should be case insensitive or not | [2] | -| `not` | `NestedStringNullableFilter` | Does not match the inner filter | | +{% table %} +- **Filter name** +- **Type** +- **Description** +- **Notes** +--- +- `equals` +- `String` +- Equals +- +--- +- `lt` +- `String` +- Less than +- +--- +- `lte` +- `String` +- Less than or equal +- +--- +- `gt` +- `String` +- Greater than +- +--- +- `gte` +- `String` +- Greater than or equal +- +--- +- `contains` +- `String` +- Contains +- [1] +--- +- `startsWith` +- `String` +- Starts with +- [1] +--- +- `endsWith` +- `String` +- Ends with +- [1] +--- +- `in` +- `[String!]` +- Is in the array +- +--- +- `notIn` +- `[String!]` +- Is not in the array +- +--- +- `mode` +- `QueryMode` (`default` or `insensitive`) +- Whether the filters should be case insensitive or not +- [2] +--- +- `not` +- `NestedStringNullableFilter` +- Does not match the inner filter +- +{% /table %} #### Notes @@ -101,16 +251,43 @@ The `json` field type does not support filters. ### timestamp -| **Filter name** | **Type** | **Description** | -| --------------- | ------------------------ | ------------------------------- | -| `equals` | `String` | Equals | -| `lt` | `String` | Less than | -| `lte` | `String` | Less than or equal | -| `gt` | `String` | Greater than | -| `gte` | `String` | Greater than or equal | -| `in` | `[String!]` | Is in the array | -| `notIn` | `[String!]` | Is not in the array | -| `not` | `DateTimeNullableFilter` | Does not match the inner filter | +{% table %} +- **Filter name** +- **Type** +- **Description** +--- +- `equals` +- `String` +- Equals +--- +- `lt` +- `String` +- Less than +--- +- `lte` +- `String` +- Less than or equal +--- +- `gt` +- `String` +- Greater than +--- +- `gte` +- `String` +- Greater than or equal +--- +- `in` +- `[String!]` +- Is in the array +--- +- `notIn` +- `[String!]` +- Is not in the array +--- +- `not` +- `DateTimeNullableFilter` +- Does not match the inner filter +{% /table %} ## Relationship type @@ -118,17 +295,35 @@ The `json` field type does not support filters. #### many: true -| **Filter name** | **Type** | **Description** | -| --------------- | --------------- | ------------------------------------------ | -| `every` | `FooWhereInput` | All related items match the nested filter | -| `some` | `FooWhereInput` | Some related items match the nested filter | -| `none` | `FooWhereInput` | No related items match the nested filter | +{% table %} +- **Filter name** +- **Type** +- **Description** +--- +- `every` +- `FooWhereInput` +- All related items match the nested filter +--- +- `some` +- `FooWhereInput` +- Some related items match the nested filter +--- +- `none` +- `FooWhereInput` +- No related items match the nested filter +{% /table %} #### many: false -| **Filter name** | **Type** | **Description** | -| --------------- | --------------- | ------------------------- | -| `foo` | `FooWhereInput` | Matches the nested filter | +{% table %} +- **Filter name** +- **Type** +- **Description** +--- +- `foo` +- `FooWhereInput` +- Matches the nested filter +{% /table %} ## Virtual type @@ -150,8 +345,10 @@ The `image` field type does not support filters. {% related-content %} {% well -heading="Query Filters Guide" -href="/docs/guides/filters" %} + heading="Query Filters Guide" + grad="grad1" + href="/docs/guides/filters" + target="" %} Query filters are an integral part of Keystone’s powerful GraphQL APIs. This guide will show you how to use filters to get the data you need from your system. {% /well %} {% /related-content %} diff --git a/docs/content/docs/guides/auth-and-access-control.md b/docs/content/docs/guides/auth-and-access-control.md index c9711e99c92..dce97de2d27 100644 --- a/docs/content/docs/guides/auth-and-access-control.md +++ b/docs/content/docs/guides/auth-and-access-control.md @@ -28,7 +28,6 @@ Here's an example: ```ts const Person = list({ - access: allowAll, fields: { name: text(), email: text({ isIndexed: 'unique' }), @@ -72,7 +71,7 @@ const session = statelessSessions({ }); ``` -You can use your own session strategy if for example, if you want to use use OAuth sessions. +Keystone also comes with a Redis session adapter, which uses a cookie to store a session ID that is looked up in a Redis database; or you can use your own session adapter (for example, if you are using OAuth sessions). {% hint kind="tip" %} Read more about [Session Stores in the Session API Docs](../config/session#session-stores). @@ -80,7 +79,7 @@ Read more about [Session Stores in the Session API Docs](../config/session#sessi ### Putting it all together -Your Keystone config should now look like this: +Your entire Keystone config should now look like this: ```ts import { config, list } from '@keystone-6/core'; @@ -105,7 +104,6 @@ const session = statelessSessions({ const lists = { Person: list({ - access: allowAll, fields: { name: text(), email: text({ isIndexed: 'unique' }), diff --git a/docs/content/docs/guides/cli.md b/docs/content/docs/guides/cli.md index b4bc02ecf8f..0f5586cb558 100644 --- a/docs/content/docs/guides/cli.md +++ b/docs/content/docs/guides/cli.md @@ -1,8 +1,9 @@ --- -title: "Command Line" -description: "Learn how to use Keystone's command line interface (CLI) to develop, build, and deploy your Keystone projects." +title: Command Line +description: >- + Learn how to use Keystone's command line interface (CLI) to develop, build, + and deploy your Keystone projects. --- - Keystone's command line interface (CLI) has been designed to help you develop, build, and deploy your Keystone project. Using the `keystone` command you can start the dev process, build and run your app in production, and control how you migrate the structure of your database as your schema changes. @@ -125,6 +126,7 @@ This is typically useful early in a project's development lifecycle, when you wa ```bash $ keystone postinstall ``` + {% hint kind="tip" %} Note: `postinstall` is an alias for `keystone build --no-ui --frozen` we recommend switching to this `build` command {% /hint %} @@ -142,6 +144,7 @@ While the recommended way to fix this problem is to start your app using `keysto ```bash $ keystone postinstall --fix ``` + {% hint kind="tip" %} Note: `postinstall --fix` is an alias for `keystone build --no-ui` we recommend switching to this `build` command {% /hint %} @@ -155,7 +158,9 @@ $ keystone build This command generates the files needed for Keystone to start in **production** mode. You should run it during the build phase of your production deployment. It will also validate that the generated files you should have committed to source control are in sync with your Keystone Schema. + ### build flags + - `--frozen` - Don't update the graphql or prisma schemas, only validate them, exits with error if the schemas don't match what keystone would generate. - `--no-prisma` - Don't build or validate the prisma schema - `--no-ui` - Don't build the AdminUI @@ -169,7 +174,9 @@ $ keystone start This command starts Keystone in **production** mode. It requires a build to have been generated (see `build` above). It will not generate or apply any database migrations - these should be run during the **build** or **release** phase of your production deployment. + ### start flags + - `--with-migrations` - Trigger prisma to run migrations as part of startup - `--no-ui` - Don't serve the AdminUI @@ -234,6 +241,7 @@ Start Keystone in production mode: ```bash yarn keystone start ``` + {% hint kind="tip" %} Note: To run migrations before you start Keystone use `keystone start --with-migrations` {% /hint %} @@ -249,8 +257,10 @@ Note: To run migrations before you start Keystone use `keystone start --with-mig {% related-content %} {% well -heading="Getting Started with create-keystone-app" -href="/docs/getting-started" %} + heading="Getting Started with create-keystone-app" + grad="grad1" + href="/docs/getting-started" + target="" %} How to use Keystone's CLI app to standup a new local project with an Admin UI & the GraphQL Playground. {% /well %} {% /related-content %} diff --git a/docs/content/docs/guides/custom-admin-ui-pages.md b/docs/content/docs/guides/custom-admin-ui-pages.md index 1c5b5b485b2..1ae24fd08dd 100644 --- a/docs/content/docs/guides/custom-admin-ui-pages.md +++ b/docs/content/docs/guides/custom-admin-ui-pages.md @@ -155,7 +155,6 @@ The snippet below uses the emotion `jsx` runtime exported from `@keystone-ui/cor // admin/pages/custom-page.tsx /** @jsxRuntime classic */ - import Link from 'next/link'; import { jsx } from '@keystone-ui/core'; import { PageContainer } from '@keystone-6/core/admin-ui/components'; diff --git a/docs/content/docs/guides/relationships.md b/docs/content/docs/guides/relationships.md index d4d1a37ef45..52a09381c9f 100644 --- a/docs/content/docs/guides/relationships.md +++ b/docs/content/docs/guides/relationships.md @@ -1,8 +1,10 @@ --- -title: "Understanding Relationships" -description: "Learn how to reason about and configure relationships in Keystone so you can bring value to your project through structured content." ---- +title: Understanding Relationships +description: >- + Learn how to reason about and configure relationships in Keystone so you can + bring value to your project through structured content. +--- Relationships are the connections you make between different lists of content in Keystone. What you build depends a great deal on what you need. This guide will show you how to reason about, and configure relationships in Keystone so you can bring value to your project through structured content. --- @@ -15,7 +17,7 @@ Keystone provides a lot of flexibility when it comes to relationships. To get wh Your needs here will define whether your relationship needs to be [one, or two-sided](#one-sided-and-two-sided-relationships). -2. **How many connections do I need on either side of my relationship?** +1. **How many connections do I need on either side of my relationship?** Understanding this will determine the kind of [cardinality](#establishing-cardinality) you need to configure. @@ -44,12 +46,12 @@ export default config({ ``` {% hint kind="tip" %} -The `many` config option relates to _cardinality_ which we explore later +The `many` config option relates to *cardinality* which we explore later {% /hint %} ## One-sided & two-sided relationships -In Keystone it’s possible to define relationships from one, or both sides of the two lists you’re connecting. We refer to these as _one-sided_, and _two-sided_ relationships: +In Keystone it’s possible to define relationships from one, or both sides of the two lists you’re connecting. We refer to these as *one-sided*, and *two-sided* relationships: ### One-sided @@ -126,7 +128,7 @@ Two-sided relationships are declared in two places, but **there is only one rela ### Self-referencing relationships -Keystone also lets you define one, and two-sided **relationships that refer to the same list**. To make a _one-sided_ Twitter style following relationship we do the following: +Keystone also lets you define one, and two-sided **relationships that refer to the same list**. To make a *one-sided* Twitter style following relationship we do the following: ```typescript{9}[1-3] import { config, list } from '@keystone-6/core'; @@ -144,7 +146,7 @@ export default config({ }); ``` -Or change this into a _two-sided_ relationship to also access the followers of every user: +Or change this into a *two-sided* relationship to also access the followers of every user: ```typescript{9-10}[1-3] import { config, list } from '@keystone-6/core'; @@ -169,17 +171,28 @@ The only relationship configuration not currently supported is having a field re ## Establishing cardinality -_Cardinality_ is a term used to describe how many items can exist on either side of a relationship. Each side can have either `one` or `many` related items. Since each relationship can have one and two sides, we have the following options available: +*Cardinality* is a term used to describe how many items can exist on either side of a relationship. Each side can have either `one` or `many` related items. Since each relationship can have one and two sides, we have the following options available: -| Relationship type | One to one | One to many | Many to many | -| ----------------- | -------------------------------------------- | ---------------------------------------- | ---------------------------------------- | -| One-sided | {% emoji symbol="❌" alt="Not supported" /%} | {% emoji symbol="✅" alt="Supported" /%} | {% emoji symbol="✅" alt="Supported" /%} | -| Two-sided | {% emoji symbol="✅" alt="Supported" /%} | {% emoji symbol="✅" alt="Supported" /%} | {% emoji symbol="✅" alt="Supported" /%} | +{% table %} +- Relationship type +- One to one +- One to many +- Many to many +--- +- One-sided +- {% emoji symbol="❌" alt="Not supported" /%} +- {% emoji symbol="✅" alt="Supported" /%} +- {% emoji symbol="✅" alt="Supported" /%} +--- +- Two-sided +- {% emoji symbol="✅" alt="Supported" /%} +- {% emoji symbol="✅" alt="Supported" /%} +- {% emoji symbol="✅" alt="Supported" /%} +{% /table %} Cardinality is defined through the `relationship` field’s `many` configuration option. It’s a boolean where: - `false` = one - - `true` = many {% hint kind="tip" %} @@ -350,8 +363,10 @@ Keystone relationships are managed using the [relationship](../fields/relationsh {% related-content %} {% well -heading="Relationship Field API Reference" -href="/docs/fields/relationship" %} + heading="Relationship Field API Reference" + grad="grad1" + href="/docs/fields/relationship" + target="" %} Defines the names, types, and configuration of Keystone fields. See all the fields and the configuration options they accept. {% /well %} {% /related-content %} diff --git a/docs/content/docs/walkthroughs/lesson-1.md b/docs/content/docs/walkthroughs/lesson-1.md index 8debe44867c..cf182fd7c85 100644 --- a/docs/content/docs/walkthroughs/lesson-1.md +++ b/docs/content/docs/walkthroughs/lesson-1.md @@ -1,8 +1,7 @@ --- -title: "Lesson 1: Installing Keystone" -description: "Learn Keystone: Lesson 1" +title: 'Lesson 1: Installing Keystone' +description: 'Learn Keystone: Lesson 1' --- - Learn how to install Keystone, create your first content type, and get an app up and running with an intuitive editing environment. ## Introduction @@ -146,11 +145,11 @@ We now have everything we need to start Keystone, so let’s do just that: yarn keystone dev ``` -In a few seconds your terminal will provide you with you a link to the Keystone Admin UI at +In a few seconds your terminal will provide you with you a link to the Keystone Admin UI at [http://localhost:3000](http://localhost:3000) ![Terminal dialog showing successful Keystone startup](https://keystonejs.s3.amazonaws.com/framework-assets/assets/walkthroughs/lesson-1/keystone-startup.png) -Head on over to where you can create your first user with a `name` and `email`: +Head on over to [http://localhost:3000/users](http://localhost:3000/users) where you can create your first user with a `name` and `email`: ![Adding a user record in Keystone Admin UI](https://keystonejs.s3.amazonaws.com/framework-assets/assets/walkthroughs/lesson-1/first-user-creation.gif) @@ -161,7 +160,11 @@ Next up, we’ll level-up our blog starter with a `post` list and connect it to ## Next lesson {% related-content %} -{% well heading="Lesson 2: Relating things" href="/docs/walkthroughs/lesson-2" %} +{% well + heading="Lesson 2: Relating things" + grad="grad1" + href="/docs/walkthroughs/lesson-2" + target="" %} Connect two content types and learn how to configure the appearance of field inputs {% /well %} {% /related-content %} diff --git a/docs/content/docs/walkthroughs/lesson-2.md b/docs/content/docs/walkthroughs/lesson-2.md index b9eb96ece18..fd8183e53c2 100644 --- a/docs/content/docs/walkthroughs/lesson-2.md +++ b/docs/content/docs/walkthroughs/lesson-2.md @@ -1,8 +1,7 @@ --- -title: "Lesson 2: Creating relationships" -description: "Learn Keystone: Lesson 2" +title: 'Lesson 2: Creating relationships' +description: 'Learn Keystone: Lesson 2' --- - Learn how to connect two content types to each other and configure how you make those connections in Admin UI. ## Where we left off @@ -267,8 +266,10 @@ export default config({ {% related-content %} {% well -heading="Lesson 3: Publishing workflows" -href="/docs/walkthroughs/lesson-3" %} + heading="Lesson 3: Publishing workflows" + grad="grad1" + href="/docs/walkthroughs/lesson-3" + target="" %} Support publishing needs with Keystone's `select` and `timestamp` fields {% /well %} diff --git a/docs/content/docs/walkthroughs/lesson-3.md b/docs/content/docs/walkthroughs/lesson-3.md index 372c26b4d99..829785cfba6 100644 --- a/docs/content/docs/walkthroughs/lesson-3.md +++ b/docs/content/docs/walkthroughs/lesson-3.md @@ -1,8 +1,8 @@ --- -title: "Lesson 3: Publishing workflows" -description: "Learn Keystone: Lesson 3" ---- +title: 'Lesson 3: Publishing workflows' +description: 'Learn Keystone: Lesson 3' +--- Learn how to create a publishing workflow to your app using Keystons’s `select` and `timestamp` fields. ## Where we left off @@ -294,7 +294,11 @@ export default config({ ## Next lesson {% related-content %} -{% well heading="Lesson 4: Auth & Sessions" href="/docs/walkthroughs/lesson-4" %} +{% well + heading="Lesson 4: Auth & Sessions" + grad="grad1" + href="/docs/walkthroughs/lesson-4" + target="" %} Add sessions, password protection, and a sign-in screen to your Keystone app {% /well %} {% /related-content %} diff --git a/docs/content/docs/walkthroughs/lesson-4.md b/docs/content/docs/walkthroughs/lesson-4.md index 33db767623e..c9d20c24507 100644 --- a/docs/content/docs/walkthroughs/lesson-4.md +++ b/docs/content/docs/walkthroughs/lesson-4.md @@ -1,8 +1,8 @@ --- -title: "Lesson 4: Auth & Sessions" -description: "Learn Keystone: Lesson 4" ---- +title: 'Lesson 4: Auth & Sessions' +description: 'Learn Keystone: Lesson 4' +--- Learn how to add passwords, session data and authentication to your Keystone app. ## Where we left off @@ -332,7 +332,11 @@ export default config( ## Next lesson {% related-content %} -{% well heading="Lesson 5: Rich Text" href="/docs/walkthroughs/lesson-5" %} +{% well + heading="Lesson 5: Rich Text" + grad="grad1" + href="/docs/walkthroughs/lesson-5" + target="" %} Add a powerful `document` field to your app and learn how to configure it to meet your needs {% /well %} diff --git a/docs/content/docs/walkthroughs/lesson-5.md b/docs/content/docs/walkthroughs/lesson-5.md index 1c4f0f13d0a..75074f99b77 100644 --- a/docs/content/docs/walkthroughs/lesson-5.md +++ b/docs/content/docs/walkthroughs/lesson-5.md @@ -1,8 +1,7 @@ --- -title: "Lesson 5: Document field" -description: "Learn Keystone: Lesson 5" +title: 'Lesson 5: Document field' +description: 'Learn Keystone: Lesson 5' --- - Learn how to implement a powerful and customisable Rich Text editing experience with Keystone’s `document` field. ## Where we left off @@ -261,10 +260,15 @@ Congratulations! You've now built a Keystone app from an empty folder and have t This lesson marks the end of this learning series. To dive deeper into Keystone's capabilities take a look at the following: {% related-content %} -{% well heading="Examples" href="/docs/examples" %} +{% well heading="Examples" grad="grad1" href="/docs/examples" target="" %} A growing collection of projects you can run locally to learn more about Keystone’s capabilities {% /well %} -{% well heading="Guides" href="/docs/guides/overview" %} + +{% well + heading="Guides" + grad="grad1" + href="/docs/guides/overview" + target="" %} Practical explanations of Keystone's fundamental building blocks {% /well %} {% /related-content %} diff --git a/docs/content/examples/auth.yaml b/docs/content/examples/auth.yaml new file mode 100644 index 00000000000..3fbe17e581a --- /dev/null +++ b/docs/content/examples/auth.yaml @@ -0,0 +1,5 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/auth +title: Authentication +kind: standalone +description: > + Adds password-based authentication to the Task Manager starter project. diff --git a/docs/content/examples/azure.yaml b/docs/content/examples/azure.yaml new file mode 100644 index 00000000000..f05d1ce4cb2 --- /dev/null +++ b/docs/content/examples/azure.yaml @@ -0,0 +1,5 @@ +url: https://github.com/aaronpowell/keystone-6-azure-example" +title: Microsoft Azure +kind: deployment +description: > + Deploys a Keystone app backend to Microsoft Azure. Based on the `with-auth` project. **One-click deployment** included. diff --git a/docs/content/examples/blog.yaml b/docs/content/examples/blog.yaml new file mode 100644 index 00000000000..53bca2bd898 --- /dev/null +++ b/docs/content/examples/blog.yaml @@ -0,0 +1,6 @@ +title: Blog +kind: standalone +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/usecase-blog +description: > + A basic Blog schema with Posts and Authors. Use this as a starting place for + learning how to use Keystone. It’s also a starter for other feature projects. diff --git a/docs/content/examples/custom-admin-ui-logo.yaml b/docs/content/examples/custom-admin-ui-logo.yaml new file mode 100644 index 00000000000..d5bf1250891 --- /dev/null +++ b/docs/content/examples/custom-admin-ui-logo.yaml @@ -0,0 +1,5 @@ +title: Custom Admin UI Logo +url: https://github.com/keystonejs/keystone/blob/main/examples/custom-admin-ui-logo +kind: standalone +description: > + Adds a custom logo component in the Admin UI. diff --git a/docs/content/examples/custom-admin-ui-navigation.yaml b/docs/content/examples/custom-admin-ui-navigation.yaml new file mode 100644 index 00000000000..eaff301911a --- /dev/null +++ b/docs/content/examples/custom-admin-ui-navigation.yaml @@ -0,0 +1,5 @@ +title: Custom Admin UI Navigation +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation +kind: standalone +description: > + Adds a custom Navigation component to the Admin UI. diff --git a/docs/content/examples/custom-admin-ui-pages.yaml b/docs/content/examples/custom-admin-ui-pages.yaml new file mode 100644 index 00000000000..29e4bec7d0c --- /dev/null +++ b/docs/content/examples/custom-admin-ui-pages.yaml @@ -0,0 +1,5 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/custom-admin-ui-pages +title: Custom Admin UI Pages +kind: standalone +description: > + Adds a custom page in the Admin UI. diff --git a/docs/content/examples/custom-admin-ui.yaml b/docs/content/examples/custom-admin-ui.yaml new file mode 100644 index 00000000000..7eeee2f7ecf --- /dev/null +++ b/docs/content/examples/custom-admin-ui.yaml @@ -0,0 +1,5 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/custom-admin-ui-logo +title: Custom Admin UI Logo +kind: standalone +description: > + Adds a custom logo component in the Admin UI. diff --git a/docs/content/examples/custom-field-type.yaml b/docs/content/examples/custom-field-type.yaml new file mode 100644 index 00000000000..2e21458af35 --- /dev/null +++ b/docs/content/examples/custom-field-type.yaml @@ -0,0 +1,5 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/custom-field +title: Custom Field Type +kind: standalone +description: > + Adds a custom field type based on the `integer` field type which lets users rate items on a 5-star scale. diff --git a/docs/content/examples/custom-field-view.yaml b/docs/content/examples/custom-field-view.yaml new file mode 100644 index 00000000000..fd1828b31e0 --- /dev/null +++ b/docs/content/examples/custom-field-view.yaml @@ -0,0 +1,5 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/custom-field-view +title: Custom Field View +kind: standalone +description: > + Adds a custom Admin UI view to a `json` field which provides a customised editing experience for users. diff --git a/docs/content/examples/default-values.yaml b/docs/content/examples/default-values.yaml new file mode 100644 index 00000000000..79f1dfcda67 --- /dev/null +++ b/docs/content/examples/default-values.yaml @@ -0,0 +1,8 @@ +title: Default Values +kind: standalone +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/default-values +description: > + Demonstrates how to use default values for fields. Builds upon the Task + Manager starter + + project. diff --git a/docs/content/examples/document-field-customisation.yaml b/docs/content/examples/document-field-customisation.yaml new file mode 100644 index 00000000000..cca500ee3f7 --- /dev/null +++ b/docs/content/examples/document-field-customisation.yaml @@ -0,0 +1,5 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/usecase-blog +title: Document Field Customisation +kind: end-to-end +description: > + Example to demonstrate customisation of Keystone's document field and document renderer. diff --git a/docs/content/examples/document-field.yaml b/docs/content/examples/document-field.yaml new file mode 100644 index 00000000000..994bea619b2 --- /dev/null +++ b/docs/content/examples/document-field.yaml @@ -0,0 +1,6 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/document-field +title: Document field +kind: standalone +description: > + Illustrates how to configure `document` fields in your + Keystone system and render their data in a frontend application. diff --git a/docs/content/examples/extend-express-app.yaml b/docs/content/examples/extend-express-app.yaml new file mode 100644 index 00000000000..7901ae3a1e1 --- /dev/null +++ b/docs/content/examples/extend-express-app.yaml @@ -0,0 +1,6 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/extend-express-app +title: REST API endpoint +kind: standalone +description: > + Demonstrates how to create REST endpoints by extending Keystone's express app and using the + Query API to execute queries against the schema. diff --git a/docs/content/examples/extend-graphql-schema-nexus.yaml b/docs/content/examples/extend-graphql-schema-nexus.yaml new file mode 100644 index 00000000000..aa54371d1a1 --- /dev/null +++ b/docs/content/examples/extend-graphql-schema-nexus.yaml @@ -0,0 +1,6 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/extend-graphql-schema-nexus +title: Extend GraphQL API with Nexus +kind: standalone +description: > + Shows you how to use `Nexus` to extend the GraphQL API provided by Keystone + with custom queries and mutations. diff --git a/docs/content/examples/extend-graphql-schema-ts.yaml b/docs/content/examples/extend-graphql-schema-ts.yaml new file mode 100644 index 00000000000..be8c2573efc --- /dev/null +++ b/docs/content/examples/extend-graphql-schema-ts.yaml @@ -0,0 +1,6 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/extend-graphql-schema-graphql-ts +title: Extend GraphQL Schema with GraphQL TS +kind: standalone +description: > + Demonstrates how to extend the GraphQL API provided by Keystone with custom queries and + mutations using graphql-ts. diff --git a/docs/content/examples/extend-graphql-schema.yaml b/docs/content/examples/extend-graphql-schema.yaml new file mode 100644 index 00000000000..613e966f456 --- /dev/null +++ b/docs/content/examples/extend-graphql-schema.yaml @@ -0,0 +1,6 @@ +title: Extend GraphQL Schema +kind: standalone +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/extend-graphql-schema +description: > + Shows you how to extend the Keystone GraphQL API with custom queries and + mutations. Builds upon the Blog starter project. diff --git a/docs/content/examples/heroku.yaml b/docs/content/examples/heroku.yaml new file mode 100644 index 00000000000..76712c85333 --- /dev/null +++ b/docs/content/examples/heroku.yaml @@ -0,0 +1,7 @@ +url: https://github.com/keystonejs/keystone-6-heroku-example" +title: Heroku +kind: deployment +description: > + Based on the `with-auth` project, this example deploys a + simple app backend to Heroku. Includes a **one-click deployment** for Heroku + account holders. diff --git a/docs/content/examples/json-field.yaml b/docs/content/examples/json-field.yaml new file mode 100644 index 00000000000..51023236789 --- /dev/null +++ b/docs/content/examples/json-field.yaml @@ -0,0 +1,6 @@ +title: JSON Field +kind: standalone +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/json +description: > + Illustrates how to use the `json` field type. Builds on the Task Manager + starter project. diff --git a/docs/content/examples/next-js.yaml b/docs/content/examples/next-js.yaml new file mode 100644 index 00000000000..3350af1e257 --- /dev/null +++ b/docs/content/examples/next-js.yaml @@ -0,0 +1,5 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/framework-nextjs-app-directory +title: Next.js + Keystone +kind: end-to-end +description: > + Shows you how to use Keystone as a data engine within Next.js applications. diff --git a/docs/content/examples/railway.yaml b/docs/content/examples/railway.yaml new file mode 100644 index 00000000000..2ba21f1f96b --- /dev/null +++ b/docs/content/examples/railway.yaml @@ -0,0 +1,7 @@ +url: https://github.com/keystonejs/keystone-6-railway-example" +title: Railway +kind: deployment +description: > + Deploys a Keystone app backend to Railway. Based on the + `with-auth` project. + **One-click deployment** included. diff --git a/docs/content/examples/singleton.yaml b/docs/content/examples/singleton.yaml new file mode 100644 index 00000000000..d45fb6d311b --- /dev/null +++ b/docs/content/examples/singleton.yaml @@ -0,0 +1,5 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/singleton +title: Singleton +kind: standalone +description: > + Demonstrates how to use singleton lists with Keystone. diff --git a/docs/content/examples/task-manager.yaml b/docs/content/examples/task-manager.yaml new file mode 100644 index 00000000000..5191e0507b4 --- /dev/null +++ b/docs/content/examples/task-manager.yaml @@ -0,0 +1,7 @@ +title: Task Manager +kind: standalone +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/usecase-todo +description: > + A basic Task Management app, with Tasks and People who can be assigned to + tasks. Great for learning how to use Keystone. It’s also a starter for other + feature projects. diff --git a/docs/content/examples/testing.yaml b/docs/content/examples/testing.yaml new file mode 100644 index 00000000000..71d09da5aa4 --- /dev/null +++ b/docs/content/examples/testing.yaml @@ -0,0 +1,5 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/testing +title: Testing +kind: standalone +description: > + Shows you how to write tests against the GraphQL API to your Keystone system. diff --git a/docs/content/examples/virtual-field.yaml b/docs/content/examples/virtual-field.yaml new file mode 100644 index 00000000000..7901dd1bdb1 --- /dev/null +++ b/docs/content/examples/virtual-field.yaml @@ -0,0 +1,5 @@ +url: https://keystonejs.com/docs/guides/custom-admin-ui-navigation/virtual-field +title: Virtual fields +kind: standalone +description: > + Implements virtual fields in a Keystone list. diff --git a/docs/content/featured-docs.yaml b/docs/content/featured-docs.yaml new file mode 100644 index 00000000000..533b6539c7b --- /dev/null +++ b/docs/content/featured-docs.yaml @@ -0,0 +1,145 @@ +groups: + - groupName: Walkthroughs + groupDescription: | + Step-by-step instructions for getting things done with Keystone. + gradient: grad2 + items: + - label: Keystone Quick Start + link: + discriminant: docs + value: + docPage: getting-started + description: | + Take a tour of Keystone in minutes with our CLI starter project + - label: 'Lesson 1: Installing Keystone' + link: + discriminant: docs + value: + docPage: walkthroughs/lesson-1 + description: | + Get Keystone up and running with your first content type + - label: 'Lesson 2: Relating things' + link: + discriminant: docs + value: + docPage: walkthroughs/lesson-2 + description: | + Connect two content types and learn how to configure the appearance of field inputs + + - label: 'Lesson 3: Publishing Workflows' + link: + discriminant: docs + value: + docPage: walkthroughs/lesson-3 + description: | + Support publishing needs with Keystone's `select` and `timestamp` fields + + - label: 'Lesson 4: Auth & Sessions' + link: + discriminant: docs + value: + docPage: walkthroughs/lesson-4 + description: | + Add sessions, password protection, and a sign-in screen to your Keystone app + - label: 'Lesson 5: Rich Text' + link: + discriminant: docs + value: + docPage: walkthroughs/lesson-5 + description: | + Add a powerful `document` field to your app and learn how to configure it to meet your needs + - groupName: Guides + groupDescription: > + Practical explanations of Keystone’s fundamental building blocks. When + you’re trying to get something done, Keystone guides show you how to think + about, and get the most out of each feature. + gradient: grad2 + items: + - label: Command Line Foundations + link: + discriminant: docs + value: + docPage: guides/cli + description: | + Keystone’s CLI helps you develop, build, and deploy projects. This guide explains all you need to standup a new backend in the terminal. + + - label: Understanding Relationships + link: + discriminant: docs + value: + docPage: guides/relationships + description: | + Learn how to reason about and configure relationships in Keystone, so you can bring value to your project through structured content. + + - label: GraphQL Queries - Filters + link: + discriminant: docs + value: + docPage: guides/filters + description: | + Query filters are an integral part of Keystone’s powerful GraphQL APIs. This guide will show you how to use filters to get the data you need from your system. + + - label: Understanding Hooks + link: + discriminant: docs + value: + docPage: guides/hooks + description: | + Learn how to use Hooks within your schema to extend Keystone’s powerful CRUD GraphQL APIs with your own business logic. + + - label: How To Use Document Fields + link: + discriminant: docs + value: + docPage: guides/document-fields + description: | + Keystone’s document field is a highly customisable rich text editor that stores content as structured JSON. Learn how to configure it and incorporate your own custom React components. + + - label: Document Field Demo + link: + discriminant: url + value: + url: /docs/guides/document-field-demo + description: | + Test drive the many features of Keystone’s Document field on this website. + + - label: Custom Fields + link: + discriminant: docs + value: + docPage: guides/custom-fields + description: | + Learn how to define your own custom field types in Keystone, with customisable backend data structure, and Admin UI appearance. + + - label: Testing Guide + link: + discriminant: docs + value: + docPage: guides/testing + description: | + Learn how to test the behaviour of your Keystone system to ensure it does what you expect. + + - label: Virtual Fields + link: + discriminant: docs + value: + docPage: guides/virtual-fields + description: | + Virtual fields offer a powerful way to extend your GraphQL API. This guide introduces the syntax and shows you how start simply and end up with a complex result. + + - label: Choosing a Database + link: + discriminant: docs + value: + docPage: guides/choosing-a-database + description: | + Keystone supports Postgres, MySQL and SQLite. This guide explains how to choose the best for your project. + + - label: Images and Files + link: + discriminant: docs + value: + docPage: guides/images-and-files + description: | + Learn how to store and manage Images and Files in Keystone. + diff --git a/docs/content/featured-examples.yaml b/docs/content/featured-examples.yaml new file mode 100644 index 00000000000..b980a9de66e --- /dev/null +++ b/docs/content/featured-examples.yaml @@ -0,0 +1,23 @@ +label: Example Projects +description: > + A growing collection of projects you can run locally to learn more about + Keystone features. Use these as a reference for best practice, and a jumping + off point when adding features to your own Keystone project. [View on Github + →](https://github.com/keystonejs/keystone/tree/main/examples) +gradient: grad3 +items: + - blog + - task-manager + - extend-graphql-schema + - default-values + - virtual-field + - document-field + - testing + - auth + - json-field + - custom-field-view + - custom-field-type + - custom-admin-ui-pages + - custom-admin-ui-logo + - custom-admin-ui-navigation + - document-field-customisation diff --git a/docs/content/navigation.yaml b/docs/content/navigation.yaml new file mode 100644 index 00000000000..3d18e22fce1 --- /dev/null +++ b/docs/content/navigation.yaml @@ -0,0 +1,285 @@ +navGroups: + - groupName: Start + items: + - label: Docs Home + link: + discriminant: url + value: /docs + status: default + - label: Walkthroughs + link: + discriminant: url + value: /docs/walkthroughs + status: default + - label: Examples + link: + discriminant: url + value: /docs/examples + status: default + - groupName: Guides + items: + - label: Overview + link: + discriminant: url + value: /docs/guides/overview + status: default + - label: Command Line + link: + discriminant: page + value: guides/cli + status: default + - label: Relationships + link: + discriminant: page + value: guides/relationships + status: default + - label: Choosing a Database + link: + discriminant: page + value: guides/choosing-a-database + status: default + - label: Database Migration + link: + discriminant: page + value: guides/database-migration + status: new + - label: Query Filters + link: + discriminant: page + value: guides/filters + status: updated + - label: Hooks + link: + discriminant: page + value: guides/hooks + status: updated + - label: Auth & Access Control + link: + discriminant: page + value: guides/auth-and-access-control + status: new + - label: Images & Files + link: + discriminant: page + value: guides/images-and-files + status: new + - label: GraphQL Schema Extension + link: + discriminant: page + value: guides/schema-extension + status: new + - label: Testing + link: + discriminant: page + value: guides/testing + status: default + - label: Document Fields + link: + discriminant: page + value: guides/document-fields + status: default + - label: Document Fields Demo + link: + discriminant: url + value: /docs/guides/document-field-demo + status: default + - label: Virtual Fields + link: + discriminant: page + value: guides/virtual-fields + status: default + - label: Custom Fields + link: + discriminant: page + value: guides/custom-fields + status: default + - label: Custom Admin UI Logo + link: + discriminant: page + value: guides/custom-admin-ui-logo + status: default + - label: Custom Admin UI Pages + link: + discriminant: page + value: guides/custom-admin-ui-pages + status: default + - label: Custom Admin UI Navigation + link: + discriminant: page + value: guides/custom-admin-ui-navigation + status: default + - groupName: Configuration + items: + - label: Overview + link: + discriminant: url + value: /docs/config/overview + status: default + - label: Config + link: + discriminant: url + value: /docs/config/config + status: default + - label: Lists + link: + discriminant: page + value: config/lists + status: default + - label: Authentication + link: + discriminant: page + value: config/auth + status: default + - label: Access Control + link: + discriminant: page + value: config/access-control + status: updated + - label: Hooks + link: + discriminant: page + value: config/hooks + status: updated + - label: Session + link: + discriminant: page + value: config/session + status: default + - groupName: Fields + items: + - label: Overview + link: + discriminant: page + value: fields/overview + status: default + - label: BigInt + link: + discriminant: page + value: fields/bigint + status: default + - label: Calendar Day + link: + discriminant: page + value: fields/calendarday + status: default + - label: Checkbox + link: + discriminant: page + value: fields/checkbox + status: default + - label: Cloudinary Image + link: + discriminant: page + value: fields/cloudinaryimage + status: default + - label: Decimal + link: + discriminant: page + value: fields/decimal + status: default + - label: Document + link: + discriminant: page + value: fields/document + status: default + - label: File + link: + discriminant: page + value: fields/file + status: default + - label: Float + link: + discriminant: page + value: fields/float + status: default + - label: Image + link: + discriminant: page + value: fields/image + status: default + - label: Integer + link: + discriminant: page + value: fields/integer + status: default + - label: JSON + link: + discriminant: page + value: fields/json + status: default + - label: Multiselect + link: + discriminant: page + value: fields/multiselect + status: default + - label: Password + link: + discriminant: page + value: fields/password + status: default + - label: Relationship + link: + discriminant: page + value: fields/relationship + status: default + - label: Select + link: + discriminant: page + value: fields/select + status: default + - label: Text + link: + discriminant: page + value: fields/text + status: default + - label: Timestamp + link: + discriminant: page + value: fields/timestamp + status: default + - label: Virtual + link: + discriminant: page + value: fields/virtual + status: default + - groupName: Context + items: + - label: Overview + link: + discriminant: page + value: context/overview + status: default + - label: getContext + link: + discriminant: page + value: context/get-context + status: default + - label: Query + link: + discriminant: page + value: context/query + status: default + - label: DB + link: + discriminant: page + value: context/db-items + status: default + - groupName: GraphQL + items: + - label: Overview + link: + discriminant: page + value: graphql/overview + status: updated + - label: Query Filters + link: + discriminant: page + value: graphql/filters + status: updated + - groupName: Reference + items: + - label: Telemetry + link: + discriminant: page + value: reference/telemetry + status: default \ No newline at end of file diff --git a/docs/keystatic.config.tsx b/docs/keystatic.config.tsx index 2881a47530f..f1c9605268b 100644 --- a/docs/keystatic.config.tsx +++ b/docs/keystatic.config.tsx @@ -1,9 +1,8 @@ -// keystatic.config.ts -import { config, fields, collection } from '@keystatic/core' +import { config, fields, collection, singleton } from '@keystatic/core' import { superscriptIcon } from '@keystar/ui/icon/icons/superscriptIcon' - import { inline, mark, wrapper } from '@keystatic/core/content-components' -// import { WellPreview } from './keystatic/admin-previews' + +import { gradientSelector } from './keystatic/gradient-selector' export default config({ storage: { @@ -13,17 +12,25 @@ export default config({ brand: { name: 'Keystone Website', }, + navigation: { + Pages: ['docs', 'posts', 'examples'], + Config: ['navigation', 'featuredDocs', 'featuredExamples'], + }, }, collections: { + // ------------------------------ + // Docs + // ------------------------------ docs: collection({ - label: 'Docs', + label: 'Docs Pages', path: 'content/docs/**', slugField: 'title', columns: ['title'], format: { contentField: 'content' }, + entryLayout: 'content', schema: { - title: fields.slug({ name: { label: 'Title' } }), - description: fields.text({ label: 'Description' }), + title: fields.slug({ name: { label: 'Title', validation: { isRequired: true } } }), + description: fields.text({ label: 'Description', validation: { isRequired: true } }), content: fields.markdoc({ label: 'Content', extension: 'md', @@ -98,10 +105,12 @@ export default config({ }, }), + // ------------------------------ // Blog + // ------------------------------ posts: collection({ + label: 'Blog Posts', path: 'content/blog/*', - label: 'Blog', slugField: 'title', columns: ['title'], format: { contentField: 'content' }, @@ -115,5 +124,163 @@ export default config({ content: fields.markdoc({ label: 'Content', extension: 'md' }), }, }), + + // ------------------------------ + // Examples + // ------------------------------ + examples: collection({ + label: 'GitHub Examples', + path: 'content/examples/*', + slugField: 'title', + schema: { + title: fields.slug({ name: { label: 'Title' } }), + kind: fields.select({ + label: 'Example Kind', + options: [ + { label: 'Standalone', value: 'standalone' }, + { label: 'End-to-End', value: 'end-to-end' }, + { label: 'Deployment', value: 'deployment' }, + ], + defaultValue: 'standalone', + }), + url: fields.text({ + label: 'URL', + validation: { isRequired: true }, + }), + description: fields.markdoc.inline({ label: 'Description' }), + }, + }), + }, + singletons: { + // ------------------------------ + // Navigation + // ------------------------------ + navigation: singleton({ + label: 'Docs Sidebar Navigation', + path: 'content/navigation', + schema: { + navGroups: fields.array( + fields.object({ + groupName: fields.text({ label: 'Group name' }), + items: fields.array( + fields.object({ + label: fields.text({ + label: 'Label', + description: "Required when using a URL, or overriding the page's title", + }), + link: fields.conditional( + fields.select({ + label: 'Link type', + options: [ + { label: 'Page', value: 'page' }, + { label: 'URL', value: 'url' }, + ], + defaultValue: 'page', + }), + { + page: fields.relationship({ + label: 'Docs Page', + collection: 'docs', + }), + url: fields.text({ label: 'URL' }), + } + ), + status: fields.select({ + label: 'Status', + options: [ + { label: 'Default', value: 'default' }, + { label: 'New', value: 'new' }, + { label: 'Updated', value: 'updated' }, + ], + defaultValue: 'default', + }), + }), + { + label: 'Navigation items', + itemLabel: (props) => props.fields.label.value, + } + ), + }), + { + label: 'Navigation groups', + itemLabel: (props) => props.fields.groupName.value, + } + ), + }, + }), + + // ------------------------------ + // Featured Docs + // ------------------------------ + featuredDocs: singleton({ + label: 'Featured Docs', + path: 'content/featured-docs', + schema: { + groups: fields.array( + fields.object({ + groupName: fields.text({ label: 'Group name' }), + groupDescription: fields.markdoc.inline({ label: 'Group description' }), + gradient: gradientSelector({ defaultValue: 'grad1' }), + items: fields.array( + fields.object({ + label: fields.text({ + label: 'Label', + description: "Required when using a URL, or overriding the page's title", + validation: { isRequired: true }, + }), + link: fields.conditional( + fields.select({ + label: 'Link type', + options: [ + { label: 'Docs Page', value: 'docs' }, + { label: 'URL', value: 'url' }, + ], + defaultValue: 'docs', + }), + { + docs: fields.object( + { + docPage: fields.relationship({ + label: 'Docs Page', + collection: 'docs', + }), + description: fields.markdoc.inline({ label: 'Description' }), + }, + { + label: 'Docs Page', + } + ), + url: fields.object({ + url: fields.text({ label: 'URL' }), + description: fields.markdoc.inline({ label: 'Description' }), + }), + } + ), + }), + { + label: 'Featured Items', + itemLabel: (props) => + `${props.fields.label.value} — [${props.fields.link.discriminant}]`, + } + ), + }), + { + label: 'Featured Groups', + itemLabel: (props) => props.fields.groupName.value, + } + ), + }, + }), + + featuredExamples: singleton({ + label: 'Featured Examples', + path: 'content/featured-examples', + schema: { + label: fields.text({ label: 'Label' }), + description: fields.markdoc.inline({ label: 'Group description' }), + gradient: gradientSelector({ defaultValue: 'grad1' }), + items: fields.multiRelationship({ label: 'Examples List', collection: 'examples' }), + }, + }), }, }) diff --git a/docs/keystatic/get-featured-docs-map.ts b/docs/keystatic/get-featured-docs-map.ts new file mode 100644 index 00000000000..f8ddbda70ad --- /dev/null +++ b/docs/keystatic/get-featured-docs-map.ts @@ -0,0 +1,76 @@ +import { type Tag, transform } from '@markdoc/markdoc' +import { reader } from './keystatic-reader' +import { baseMarkdocConfig } from '../markdoc/config' + +export type FeaturedDocsMap = Awaited> + +export async function getFeaturedDocsMap () { + const featured = await reader.singletons.featuredDocs.read({ resolveLinkedFiles: true }) + + if (!featured) throw new Error('No featured list found') + + // We need the docs and examples data as well + const [docs, examples] = await Promise.all([ + reader.collections.docs.all(), + reader.collections.examples.all(), + ]) + + // Individual doc and example accessors + const docsBySlug = new Map(docs.map((doc) => [doc.slug, doc])) + const examplesBySlug = new Map(examples.map((example) => [example.slug, example])) + + // Each `item` will need to be processed differently based on the `link` discriminant + async function processItem ({ label, link, wide, gradient }) { + const { discriminant, value } = link + let description: Tag | null = null + let href: string = '#' + + switch (discriminant) { + case 'url': { + description = transform(value.description.node, baseMarkdocConfig) as Tag + href = value.url + break + } + case 'docs': { + const docPage = value.docPage ? docsBySlug.get(value.docPage) : null + if (!docPage) throw new Error(`No doc page found for slug: ${value.docPage}`) + description = transform(value.description.node, baseMarkdocConfig) as Tag + href = docPage.slug ? `/docs/${docPage.slug}` : '#' + break + } + case 'example': { + const example = value.example ? examplesBySlug.get(value.example) : null + const hasDescriptionOverride = !!value.descriptionOverride.discriminant + let descNode + if (hasDescriptionOverride) { + descNode = value.descriptionOverride.value.node + } else { + if (!example) throw new Error(`No example found for slug: ${value.example}`) + const awaited = await example.entry.description() + descNode = awaited.node + } + description = transform(descNode, baseMarkdocConfig) as Tag + href = example?.entry.url || '#' + break + } + } + + return { label, description, href, wide, gradient } + } + + // Processing all groups... + const processedGroups = await Promise.all( + featured.navGroups.map(async ({ groupName, groupDescription, gradient, items }) => { + const transformedGroupDescription = transform(groupDescription.node, baseMarkdocConfig) as Tag + const processedItems = await Promise.all(items.map(processItem)) + + return { + groupName, + groupDescription: transformedGroupDescription, + gradient, + items: processedItems, + } + }) + ) + return processedGroups +} diff --git a/docs/keystatic/get-navigation-map.ts b/docs/keystatic/get-navigation-map.ts new file mode 100644 index 00000000000..14e55b29dad --- /dev/null +++ b/docs/keystatic/get-navigation-map.ts @@ -0,0 +1,27 @@ +import { reader } from './keystatic-reader' + +export type NavigationMap = Awaited> + +export async function getNavigationMap () { + const navigation = await reader.singletons.navigation.read() + const pages = await reader.collections.docs.all() + + const pagesBySlug = Object.fromEntries(pages.map((page) => [page.slug, page])) + + const navigationMap = navigation?.navGroups.map(({ groupName, items }) => ({ + groupName, + items: items.map(({ label, link, status }) => { + const { discriminant, value } = link + const page = discriminant === 'page' && value ? pagesBySlug[value] : null + const url = discriminant === 'url' ? value : `/docs/${page?.slug}` + + return { + label: label || page?.entry.title || '', + href: url || '', + status, + } + }), + })) + + return navigationMap +} diff --git a/docs/keystatic/gradient-selector.ts b/docs/keystatic/gradient-selector.ts new file mode 100644 index 00000000000..4434f234a54 --- /dev/null +++ b/docs/keystatic/gradient-selector.ts @@ -0,0 +1,17 @@ +import { fields } from '@keystatic/core' + +type Gradient = 'grad1' | 'grad2' | 'grad3' | 'grad4' + +export function gradientSelector ({ defaultValue = 'grad1' }: { defaultValue: Gradient }) { + return fields.select({ + label: 'Gradient', + description: 'The gradient to use for the group', + options: [ + { label: '1', value: 'grad1' }, + { label: '2', value: 'grad2' }, + { label: '3', value: 'grad3' }, + { label: '4', value: 'grad4' }, + ], + defaultValue: defaultValue, + }) +} diff --git a/docs/keystatic/keystatic-reader.ts b/docs/keystatic/keystatic-reader.ts new file mode 100644 index 00000000000..9efdbbf9109 --- /dev/null +++ b/docs/keystatic/keystatic-reader.ts @@ -0,0 +1,4 @@ +import { createReader } from '@keystatic/core/reader' +import keystaticConfig from '../keystatic.config' + +export const reader = createReader(process.cwd(), keystaticConfig)