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:

rich text bio
Figure 1. Lea Seydoux with rich text bio content

Task: Rich text rendering

Let’s take bio input field from Person content type and see how to render it.

  1. 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.ts
    import {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

  2. 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 the RichTextView component.
    src/components/views/PersonWithBio.tsx
    import 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;
    }
  3. Update the mapping

    Finally, we need to update the mappings to use the new query and view.

    Simply replace the getPerson query with getPersonWithBio and Person view with PersonWithBio. It should look something like this:

    src/components/_mappings.ts
    ComponentRegistry.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.

  1. 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.tsx
    import {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;
  2. 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 the heading.
    Macro must be registered before any component that uses it! Best practice is to register it at the top of the _mappings.ts file.
  3. 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.


Contents

Contents