Rich Text
Contents
In this chapter you’ll learn how to render rich text content.
Intro
Enonic boasts a rich text field field type aka HtmlArea. Unlike basic text fields, rich text may contain more complex concepts such as images, links and custom components (aka macros). Each of these will typically need to be processed during rendering in order to provide a contextually correct URL, ideal image sizes - not to mention rendering of custom components.
Sample content
Luckily, our sample data set contains everyting we need to get going. The Bio field for persons is rich text, and some of the content is even populated with relevant content, like you see below:
Task: Rich text rendering
Let’s take bio
input field from Person
content type and see how to render it.
-
Update the Person query
To properly render Rich text, we need additional metadata about links and images.
To simplify things, Next.XP provides a
richTextQuery
helper function that generates the query for us. Also, this way we don’t have to repeat the query for every rich text field.Add a new query file to your project:
src/components/queries/getPersonWithBio.tsimport {APP_NAME_UNDERSCORED, richTextQuery} from '@enonic/nextjs-adapter'; const getPerson = () => ` query($path:ID!){ guillotine { get(key:$path) { displayName ... on ${APP_NAME_UNDERSCORED}_Person { data { ${richTextQuery('bio')} dateofbirth photos { ... on media_Image { imageUrl: imageUrl(type: absolute, scale: "width(500)") attachments { name } } } } } parent { _path(type: siteRelative) } } } }`; export default getPerson;
The difference is this line: ${richTextQuery('bio')}`
, and that getPerson must now be a function.For further insight, visit the rich text chapter in the Developer 101 tutorial
-
The RichTextView component
We also need an updated view component that will render the
bio
.It reads the bio
data from the query response, and passes it to theRichTextView
component.src/components/views/PersonWithBio.tsximport React from 'react' import {FetchContentResult, getUrl, I18n} from '@enonic/nextjs-adapter'; import Link from 'next/link'; import RichTextView from '@enonic/nextjs-adapter/views/RichTextView'; import styles from './PersonWithBio.module.css'; const Person = (props: FetchContentResult) => { const {displayName, data, parent} = props.data?.get as any; const {bio, photos} = data; const meta = props.meta; return ( <> <div className={styles.person}> <h2>{displayName}</h2> <RichTextView className={styles.bio} data={bio} meta={meta}></RichTextView> { photos.length && <h4 className={styles.photosheader}>Photos</h4> } <div className={styles.photos}> { photos.map((photo: any, i: number) => ( <img key={i} src={getUrl(photo.imageUrl, meta)} title={getTitle(photo, displayName)} alt={getTitle(photo, displayName)} width="500" /> )) } </div> </div> <p><Link href={getUrl(`/${parent._path}`, meta)}>{I18n.localize('back')}</Link></p> </> ) } export default Person; function getTitle(photo: any, displayName: string) { return (photo.attachments || [])[0].name || displayName; }
-
Update the mapping
Finally, we need to update the mappings to use the new query and view.
Simply replace the
getPerson
query withgetPersonWithBio
andPerson
view withPersonWithBio
. It should look something like this:src/components/_mappings.tsComponentRegistry.addContentType(`${APP_NAME}:person`, { query: getPersonWithBio(), view: PersonWithBio });
That’s it!
The result should look something like this: TODO Screenshot
Macros
Macros enable you to add custom components inside your rich text. In order to render in your front-end, every macro need to be to registered as a component.
Filmography macro
In your Enonic project, a filmography
macro has been pre-defined. You can find it in src/main/resources/site/macros/filmography/filmography.xml
If you have a closer look at the Léa Seydoux content once more, you should see the following text snippet at the end of the bio text:
[filmography heading="Lea's Movies" _document="__macroDocument1"/]
This means, an editor has added the filmography
macro, with a single heading
parameter to the rich text.
Task: Render a macro
But there is no trace of it in the preview pane. That is because we need to create a React component that will render it.
-
Add Filmography component
This React component will automatically be invoked by the
RichTextView
component, if it encounters the filmography macro tag.src/components/macros/FilmographyMacro.tsximport {MacroProps} from '@enonic/nextjs-adapter'; import React from 'react' import Link from 'next/link'; const FilmographyMacro = ({name, config, meta}: MacroProps) => { // macro is used inside a <p> tag so we can't use any block tags const prefix = meta.baseUrl + (meta.locale && meta.locale !== meta.defaultLocale ? meta.locale + '/' : ''); const heading = config.heading || 'Filmography'; return config.movies.length ? <> <h4 className={"filmography-heading"}>{heading}</h4> <ul>{ config.movies.map((movie: any, i: number) => ( <li className={"filmography-movie"} key={i}> <Link href={prefix + movie._path}>{movie.displayName}</Link> </li> )) }</ul> </> : null }; export default FilmographyMacro;
-
Register macro
In order for the
RichTextView
to be aware of your new macro, you must register it - just like any other component:src/components/_mappings.ts... import Filmography from './macros/Filmography'; ... ComponentRegistry.addMacro(`${APP_NAME}:filmography`, { view: Filmography, configQuery: '{ heading }' });
configQuery
basically lets you ask for configuration values for your component, in this specific case theheading
.Macro must be registered before any component that uses it! Best practice is to register it at the top of the _mappings.ts
file. -
Congratulations!
You should now be able to see macro render
Lea’s Movies
within the rich text: http://localhost:3000/persons/lea-seydoux!
In the next chapter we’ll make it possible to create pages editorially.