docs/content/docs/walkthroughs/lesson-5.md
Learn how to implement a powerful and customisable Rich Text editing experience with Keystone’s document field.
We have a working backend for a collection of interconnected posts and authors (in the User list), and thanks to our efforts in lesson 4 our app is now secured so that only registered users can access the Admin UI to write and edit posts.
//keystone.ts
import { list, config } from '@keystone-6/core'
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3'
import { password, text, timestamp, select, relationship } from '@keystone-6/core/fields'
import { withAuth, session } from './auth'
const lists = {
User: list({
fields: {
name: text({ validation: { isRequired: true } }),
email: text({ validation: { isRequired: true }, isIndexed: 'unique' }),
posts: relationship({ ref: 'Post.author', many: true }),
password: password({ validation: { isRequired: true } }),
},
}),
Post: list({
fields: {
title: text(),
publishedAt: timestamp(),
status: select({
options: [
{ label: 'Published', value: 'published' },
{ label: 'Draft', value: 'draft' },
],
defaultValue: 'draft',
ui: { displayMode: 'segmented-control' },
}),
author: relationship({ ref: 'User.posts' }),
},
}),
}
export default config(
withAuth({
db: {
provider: 'sqlite',
prismaClientOptions: () => ({
adapter: new PrismaBetterSqlite3({ url: 'file:./keystone.db' }),
}),
},
lists,
session,
ui: {
isAccessAllowed: context => !!context.session?.data,
},
})
)
Back in Lesson 2 we setup a post type, but we skipped the all important "content" part. Let’s go ahead and fill that gap.
Keystone’s document field is a highly customisable Rich Text editor that lets content creators quickly and easily edit content in your system.
To implement the document field we start by adding the package to our project:
npm install @keystone-6/fields-document
Next, we add the document field to our post list:
// keystone.ts
import { list, config } from '@keystone-6/core';
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
import { password, text, timestamp, select, relationship } from '@keystone-6/core/fields';
import { document } from '@keystone-6/fields-document';
import { withAuth, session } from './auth';
const lists = {
User: list({
fields: {
name: text({ validation: { isRequired: true } }),
email: text({ validation: { isRequired: true }, isIndexed: 'unique' }),
posts: relationship({ ref: 'Post.author', many: true }),
password: password({ validation: { isRequired: true } })
},
}),
Post: list({
fields: {
title: text(),
publishedAt: timestamp(),
status: select({
options: [
{ label: 'Published', value: 'published' },
{ label: 'Draft', value: 'draft' },
],
defaultValue: 'draft',
}),
author: relationship({ ref: 'User.posts' }),
content: document()
},
}),
};
export default config(
withAuth({
db: {
provider: 'sqlite',
prismaClientOptions: () => ({
adapter: new PrismaBetterSqlite3({ url: 'file:./keystone.db' }),
}),
},
lists,
session,
ui: { isAccessAllowed: (context) => !!context.session?.data },
})
);
Let’s startup Keystone to see the document field in a post:
We have a place to write Rich Text, but the control header needs configuring to make this a good editor experience.
Let’s start by adding four formatting options available in the document field’s config:
// keystone.ts
import { list, config } from '@keystone-6/core';
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
import { password, text, timestamp, select, relationship } from '@keystone-6/core/fields';
import { document } from '@keystone-6/fields-document';
import { withAuth, session } from './auth';
const lists = {
User: list({
fields: {
name: text({ validation: { isRequired: true } }),
email: text({ validation: { isRequired: true }, isIndexed: 'unique' }),
posts: relationship({ ref: 'Post.author', many: true }),
password: password({ validation: { isRequired: true } })
},
}),
Post: list({
fields: {
title: text(),
publishedAt: timestamp(),
status: select({
options: [
{ label: 'Published', value: 'published' },
{ label: 'Draft', value: 'draft' },
],
defaultValue: 'draft',
}),
author: relationship({ ref: 'User.posts' }),
content: document({
formatting: true,
links: true,
dividers: true,
layouts: [
[1, 1],
[1, 1, 1],
[2, 1],
[1, 2],
[1, 2, 1],
],
}),
},
}),
};
export default config(
withAuth({
db: {
provider: 'sqlite',
prismaClientOptions: () => ({
adapter: new PrismaBetterSqlite3({ url: 'file:./keystone.db' }),
}),
},
lists,
session,
ui: { isAccessAllowed: (context) => !!context.session?.data },
})
);
And take a look at how they can be used in Admin UI:
For a full guide on using the document field, including rendering this in your own frontend app, check out the full guide on how to use document fields
// keystone.ts
import { list, config } from '@keystone-6/core'
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3'
import { password, text, timestamp, select, relationship } from '@keystone-6/core/fields'
import { document } from '@keystone-6/fields-document'
import { withAuth, session } from './auth'
const lists = {
User: list({
fields: {
name: text({ validation: { isRequired: true } }),
email: text({ validation: { isRequired: true }, isIndexed: 'unique' }),
posts: relationship({ ref: 'Post.author', many: true }),
password: password({ validation: { isRequired: true } }),
},
}),
Post: list({
fields: {
title: text(),
publishedAt: timestamp(),
status: select({
options: [
{ label: 'Published', value: 'published' },
{ label: 'Draft', value: 'draft' },
],
defaultValue: 'draft',
}),
author: relationship({ ref: 'User.posts' }),
content: document({
formatting: true,
links: true,
dividers: true,
layouts: [
[1, 1],
[1, 1, 1],
[2, 1],
[1, 2],
[1, 2, 1],
],
}),
},
}),
}
export default config(
withAuth({
db: {
provider: 'sqlite',
prismaClientOptions: () => ({
adapter: new PrismaBetterSqlite3({ url: 'file:./keystone.db' }),
}),
},
lists,
session,
ui: { isAccessAllowed: context => !!context.session?.data },
})
)
Congratulations! You've now built a Keystone app from an empty folder and have the basics in place for a functional blog that you can could connect to a frontend of your choice.
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" 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" grad="grad1" href="/docs/guides/overview" target="" %} Practical explanations of Keystone's fundamental building blocks {% /well %} {% /related-content %}