Replaced contentlayer with keystatic
This commit is contained in:
9
packages/cms/keystatic/README.md
Normal file
9
packages/cms/keystatic/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# CMS/Keystatic - @kit/keystatic
|
||||
|
||||
Implementation of the CMS layer using the Keystatic library.
|
||||
|
||||
This implementation is used when the host app's environment variable is set as:
|
||||
|
||||
```
|
||||
CMS_CLIENT=keystatic
|
||||
```
|
||||
49
packages/cms/keystatic/package.json
Normal file
49
packages/cms/keystatic/package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@kit/keystatic",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"format": "prettier --check \"**/*.{ts,tsx}\"",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./admin": "./src/keystatic-admin.tsx",
|
||||
"./route-handler": "./src/keystatic-route-handler.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@keystatic/core": "0.5.11",
|
||||
"@keystatic/next": "5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@kit/cms": "workspace:^",
|
||||
"@kit/ui": "workspace:^",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/cms": "*",
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@kit/ui": "*",
|
||||
"@types/node": "^20.12.7",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@kit/eslint-config/base",
|
||||
"@kit/eslint-config/react"
|
||||
]
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
124
packages/cms/keystatic/src/client.ts
Normal file
124
packages/cms/keystatic/src/client.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { Entry, createReader } from '@keystatic/core/reader';
|
||||
|
||||
import { Cms, CmsClient } from '@kit/cms';
|
||||
|
||||
import config from './keystatic.config';
|
||||
|
||||
const reader = createReader('.', config);
|
||||
|
||||
type EntryProps = Entry<(typeof config)['collections']['posts']>;
|
||||
|
||||
export class KeystaticClient implements CmsClient {
|
||||
async getContentItems(options?: Cms.GetContentItemsOptions) {
|
||||
const docs = await reader.collections.posts.all();
|
||||
|
||||
const startOffset = options?.offset ?? 0;
|
||||
const endOffset = startOffset + (options?.limit ?? 10);
|
||||
|
||||
return Promise.all(
|
||||
docs
|
||||
.filter((item) => {
|
||||
const categoryMatch = options?.categories
|
||||
? options.categories.find((category) =>
|
||||
item.entry.categories.includes(category),
|
||||
)
|
||||
: true;
|
||||
|
||||
if (!categoryMatch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tagMatch = options?.tags
|
||||
? options.tags.find((tag) => item.entry.tags.includes(tag))
|
||||
: true;
|
||||
|
||||
if (!tagMatch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.slice(startOffset, endOffset)
|
||||
.map(async (item) => {
|
||||
const children = docs.filter(
|
||||
(item) => item.entry.parent === item.slug,
|
||||
);
|
||||
|
||||
console.log(item);
|
||||
|
||||
return this.mapPost(item, children);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async getContentItemById(id: string) {
|
||||
const doc = await reader.collections.posts.read(id);
|
||||
|
||||
if (!doc) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return this.mapPost({ entry: doc, slug: id }, []);
|
||||
}
|
||||
|
||||
async getCategories() {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
async getTags() {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
async getTagBySlug() {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
async getCategoryBySlug() {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
private async mapPost<
|
||||
Type extends {
|
||||
entry: EntryProps;
|
||||
slug: string;
|
||||
},
|
||||
>(item: Type, children: Type[] = []): Promise<Cms.ContentItem> {
|
||||
const publishedAt = item.entry.publishedAt
|
||||
? new Date(item.entry.publishedAt)
|
||||
: new Date();
|
||||
|
||||
const content = await item.entry.content();
|
||||
|
||||
return {
|
||||
id: item.slug,
|
||||
title: item.entry.title,
|
||||
url: item.slug,
|
||||
slug: item.slug,
|
||||
description: item.entry.description,
|
||||
publishedAt,
|
||||
author: item.entry.author,
|
||||
content,
|
||||
image: item.entry.image ?? undefined,
|
||||
categories:
|
||||
item.entry.categories.map((item) => {
|
||||
return {
|
||||
id: item,
|
||||
name: item,
|
||||
slug: item,
|
||||
};
|
||||
}) ?? [],
|
||||
tags: item.entry.tags.map((item) => {
|
||||
return {
|
||||
id: item,
|
||||
name: item,
|
||||
slug: item,
|
||||
};
|
||||
}),
|
||||
parentId: item.entry.parent ?? undefined,
|
||||
order: item.entry.order ?? 1,
|
||||
children: await Promise.all(
|
||||
children.map(async (child) => this.mapPost(child, [])),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
6
packages/cms/keystatic/src/content-renderer.tsx
Normal file
6
packages/cms/keystatic/src/content-renderer.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import { DocumentElement } from '@keystatic/core';
|
||||
import { DocumentRenderer } from '@keystatic/core/renderer';
|
||||
|
||||
export function KeystaticDocumentRenderer(props: { content: unknown }) {
|
||||
return <DocumentRenderer document={props.content as DocumentElement[]} />;
|
||||
}
|
||||
2
packages/cms/keystatic/src/index.ts
Normal file
2
packages/cms/keystatic/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './client';
|
||||
export * from './content-renderer';
|
||||
7
packages/cms/keystatic/src/keystatic-admin.tsx
Normal file
7
packages/cms/keystatic/src/keystatic-admin.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { makePage } from '@keystatic/next/ui/app';
|
||||
|
||||
import config from './keystatic.config';
|
||||
|
||||
export default makePage(config);
|
||||
7
packages/cms/keystatic/src/keystatic-route-handler.tsx
Normal file
7
packages/cms/keystatic/src/keystatic-route-handler.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { makeRouteHandler } from '@keystatic/next/route-handler';
|
||||
|
||||
import keystaticConfig from './keystatic.config';
|
||||
|
||||
export const { POST, GET } = makeRouteHandler({
|
||||
config: keystaticConfig,
|
||||
});
|
||||
57
packages/cms/keystatic/src/keystatic.config.ts
Normal file
57
packages/cms/keystatic/src/keystatic.config.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { collection, config, fields } from '@keystatic/core';
|
||||
import { z } from 'zod';
|
||||
|
||||
const path = z.string().parse(process.env.NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH);
|
||||
|
||||
export default createKeyStaticConfig(path);
|
||||
|
||||
function createKeyStaticConfig(path: string) {
|
||||
return config({
|
||||
storage: {
|
||||
kind: 'local',
|
||||
},
|
||||
collections: {
|
||||
posts: collection({
|
||||
label: 'Posts',
|
||||
slugField: 'title',
|
||||
path: `${path}/posts/*`,
|
||||
format: { contentField: 'content' },
|
||||
schema: {
|
||||
title: fields.slug({ name: { label: 'Title' } }),
|
||||
image: fields.image({
|
||||
label: 'Image',
|
||||
directory: 'public/site/images',
|
||||
publicPath: '/site/images',
|
||||
}),
|
||||
categories: fields.array(fields.text({ label: 'Category' })),
|
||||
tags: fields.array(fields.text({ label: 'Tag' })),
|
||||
description: fields.text({ label: 'Description' }),
|
||||
publishedAt: fields.date({ label: 'Published At' }),
|
||||
author: fields.text({ label: 'Author' }),
|
||||
parent: fields.relationship({
|
||||
label: 'Parent',
|
||||
collection: 'posts',
|
||||
}),
|
||||
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.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
8
packages/cms/keystatic/tsconfig.json
Normal file
8
packages/cms/keystatic/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@kit/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user