rich-text
typeProvides structured content which can embed custom templates that you define, much like the object
type.
// .tina/schema.ts
import { defineSchema } from '@tinacms/cli'
export default defineSchema({
collections: [
{
label: 'Blog Posts',
name: 'post',
// This assumes that you have a /content/post directory
path: 'content/post',
fields: [
// ...
{
type: 'rich-text',
label: 'Body',
name: 'body',
isBody: true,
templates: [
{
name: 'Cta',
label: 'Call to Action',
fields: [
{
type: 'string',
name: 'heading',
label: 'Heading',
},
],
},
],
},
],
},
],
})
Given a markdown file like this:
## Hello, world!
This is some text
<Cta heading="Welcome" />
Results in the following response from the content API:
Notice the
body
response, it's a structured object instead of a string!
<TinaMarkdown>
Since the value for rich-text
is a structured object
instead of a string
, rendering it out to your page requires more work. We've provided a special TinaMarkdown
component which is super lightweight and can used directly in your code.
// [slug].js
import { TinaMarkdown } from 'tinacms/dist/rich-text'
import { staticRequest } from 'tinacms'
// The `props` here are based off our custom "Cta" MDX component
const Cta = props => {
return <h2>{props.heading}</h2>
}
// Be sure to provide the appropriate components for each template you define
const components = {
Cta: Cta,
}
export default function MyPage(props) {
return (
<div>
<TinaMarkdown
components={components}
content={props.data.getPostDocument.data.body}
/>
</div>
)
}
// See /docs/features/data-fetching/ for more info on our getStaticProps/getStaticPaths data-fetching with NextJS
export const getStaticPaths = async () => {
const tinaProps = await staticRequest({
query: `{
getPostList{
edges {
node {
sys {
filename
}
}
}
}
}`,
variables: {},
})
const paths = tinaProps.getPostList.edges.map(x => {
return { params: { slug: x.node.sys.filename } }
})
return {
paths,
fallback: 'blocking',
}
}
export const getStaticProps = async ctx => {
const query = `query getPost($relativePath: String!) {
getPostDocument(relativePath: $relativePath) {
data {
body
}
}
}
`
const variables = {
relativePath: ctx.params.slug + '.mdx',
}
let data = {}
data = await staticRequest({
query,
variables,
})
return {
props: {
data,
query,
variables,
},
}
}
You will notice the TinaMarkdown
component takes two props:
type TinaMarkdown = ({
// The rich-text data returned from the content API
content: TinaMarkdownContent
/**
* Any templates provided in the rich-text field.
* Optionally, most elements (ex. <a>) can also
* be overridden
*/
components?: Components<{}>
}) => JSX.Element
If you've worked with MDX before, you know that there's usually a compilation step which turns your .mdx
file into JavaScript code.
This works really well for developers who can access their files directly, but it creates problems when editing content from a rich-text interface.
With Tina, we're leveraging a subset of MDX to enable what's most important to content editors, and in doing so it's necessary to narrow the scope
of what's supported:
template
In the above example, if you failed to add the Cta
template in your schema definition, you would receive an error:
Found unregistered JSX or HTML: <Cta>. Please ensure all structured elements have been registered with your schema.
When we say serializable, we mean that they must not be JavaScript expressions that would need to be executed at any point.
import
/export
const a = 2
, console.log("Hello")
)For example:
## Today is {new Date().toLocaleString()}
This expression will be ignored, instead register a "Date" template
:
## Today is <Date />
Then you can create a Date
component which returns new Date().toLocaleString()
under the hood.
components
yourselfTraditionally, MDX will compile whatever components you import
so they're in scope when it's time to render your content.
But with Tina, MDX is completely decoupled from your JavaScript so it's up to you to ensure that for every template
in your rich-text
definition, there's an equivalent component
in your <TinaMarkdown>
component.