diff --git a/packages/cms/keystatic/package.json b/packages/cms/keystatic/package.json index 2b29cec1d..2ab8dc6c4 100644 --- a/packages/cms/keystatic/package.json +++ b/packages/cms/keystatic/package.json @@ -16,7 +16,8 @@ }, "dependencies": { "@keystatic/core": "0.5.14", - "@keystatic/next": "5.0.0" + "@keystatic/next": "5.0.0", + "@markdoc/markdoc": "^0.4.0" }, "devDependencies": { "@kit/cms": "workspace:^", @@ -25,6 +26,8 @@ "@kit/tsconfig": "workspace:*", "@kit/ui": "workspace:^", "@types/node": "^20.12.8", + "@types/react": "^18.3.1", + "react": "18.3.1", "zod": "^3.23.6" }, "eslintConfig": { diff --git a/packages/cms/keystatic/src/content-renderer.tsx b/packages/cms/keystatic/src/content-renderer.tsx index 5dfc181ec..40040611c 100644 --- a/packages/cms/keystatic/src/content-renderer.tsx +++ b/packages/cms/keystatic/src/content-renderer.tsx @@ -1,6 +1,7 @@ -import { DocumentElement } from '@keystatic/core'; -import { DocumentRenderer } from '@keystatic/core/renderer'; +import * as React from 'react'; -export function KeystaticDocumentRenderer(props: { content: unknown }) { - return ; +import Markdoc from '@markdoc/markdoc'; + +export function KeystaticDocumentRenderer({ content }: { content: unknown }) { + return Markdoc.renderers.react(content as string, React); } diff --git a/packages/cms/keystatic/src/keystatic-client.ts b/packages/cms/keystatic/src/keystatic-client.ts index 8c1bf416e..ffb9ab7fa 100644 --- a/packages/cms/keystatic/src/keystatic-client.ts +++ b/packages/cms/keystatic/src/keystatic-client.ts @@ -1,3 +1,5 @@ +import Markdoc from '@markdoc/markdoc'; + import { Cms, CmsClient } from '@kit/cms'; import { createKeystaticReader } from './create-reader'; @@ -138,7 +140,8 @@ class KeystaticClient implements CmsClient { ? new Date(item.entry.publishedAt) : new Date(); - const content = await item.entry.content(); + const markdoc = await item.entry.content(); + const content = Markdoc.transform(markdoc.node); return { id: item.slug, diff --git a/packages/cms/keystatic/src/keystatic.config.ts b/packages/cms/keystatic/src/keystatic.config.ts index 19efb7da0..c3141bfdb 100644 --- a/packages/cms/keystatic/src/keystatic.config.ts +++ b/packages/cms/keystatic/src/keystatic.config.ts @@ -1,15 +1,24 @@ -import { collection, config, fields } from '@keystatic/core'; +import { + CloudConfig, + GitHubConfig, + LocalConfig, + collection, + config, + fields, +} from '@keystatic/core'; import { Entry } from '@keystatic/core/reader'; import { z } from 'zod'; +type ZodOutputFor = z.ZodType; + const local = z.object({ kind: z.literal('local'), -}); +}) satisfies ZodOutputFor; const cloud = z.object({ kind: z.literal('cloud'), project: z.string(), -}); +}) satisfies ZodOutputFor; const github = z.object({ kind: z.literal('github'), @@ -19,7 +28,7 @@ const github = z.object({ githubToken: z.string({ required_error: 'Please provide a GitHub token', }), -}); +}) satisfies ZodOutputFor; const storage = z.union([local, cloud, github]).parse({ kind: process.env.KEYSTATIC_STORAGE_KIND ?? 'local', @@ -34,6 +43,34 @@ const keyStaticConfig = createKeyStaticConfig(); export default keyStaticConfig; +function getContentField() { + return fields.markdoc({ + label: 'Content', + options: { + link: true, + blockquote: true, + bold: true, + divider: true, + orderedList: true, + unorderedList: true, + strikethrough: true, + heading: true, + code: true, + italic: true, + image: { + directory: 'public/site/images', + publicPath: '/site/images', + schema: { + title: fields.text({ + label: 'Caption', + description: 'The text to display under the image in a caption.', + }), + }, + }, + }, + }); +} + export type PostEntryProps = Entry< (typeof keyStaticConfig)['collections']['posts'] >; @@ -64,23 +101,7 @@ function createKeyStaticConfig() { }), language: fields.text({ label: 'Language' }), order: fields.number({ label: 'Order' }), - content: fields.document({ - label: 'Content', - formatting: true, - dividers: true, - links: true, - images: { - directory: 'public/site/images', - publicPath: '/site/images', - schema: { - title: fields.text({ - label: 'Caption', - description: - 'The text to display under the image in a caption.', - }), - }, - }, - }), + content: getContentField(), }, }), documentation: collection({ @@ -90,23 +111,7 @@ function createKeyStaticConfig() { format: { contentField: 'content' }, schema: { title: fields.slug({ name: { label: 'Title' } }), - content: fields.document({ - label: 'Content', - formatting: true, - dividers: true, - links: true, - images: { - directory: 'public/site/images', - publicPath: '/site/images', - schema: { - title: fields.text({ - label: 'Caption', - description: - 'The text to display under the image in a caption.', - }), - }, - }, - }), + content: getContentField(), image: fields.image({ label: 'Image', directory: 'public/site/images', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c41715b61..fca3ac2bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -426,6 +426,9 @@ importers: '@keystatic/next': specifier: 5.0.0 version: 5.0.0(@keystatic/core@0.5.14)(next@14.2.3)(react-dom@18.3.1)(react@18.3.1) + '@markdoc/markdoc': + specifier: ^0.4.0 + version: 0.4.0(@types/react@18.3.1)(react@18.3.1) devDependencies: '@kit/cms': specifier: workspace:^ @@ -445,6 +448,12 @@ importers: '@types/node': specifier: ^20.12.8 version: 20.12.10 + '@types/react': + specifier: ^18.3.1 + version: 18.3.1 + react: + specifier: 18.3.1 + version: 18.3.1 zod: specifier: ^3.23.6 version: 3.23.6