Rich text

Contents

In this chapter you’ll learn how to render rich text content.

Intro

Enonic boasts a rich text input type. Unlike basic text fields, rich-text may contain more complex concepts such as tables, images, links and even custom components (aka macros). Each of these typically need custom processing during rendering, in order to provide a contextually correct URLs, image sizes etc.

Sample content

Again, the sample data set contains everything we need to get going. The Bio field in the person content type is exactly what we’re looking for:

rich text bio
Figure 1. Rich text bio field for Lea Seydoux

Rich text rendering

We’ll be using our existing Person component, and add support for the Bio field.

Follow the steps below:

  1. Update the Person processor to include a bio prop:

    /src/main/resources/react4xp/components/content/PersonProcessor.ts
    import {processHtml} from '/lib/enonic/react4xp';
    import {get as getContentByKey} from '/lib/xp/content';
    import {imageUrl} from '/lib/xp/portal';
    import {toArray} from "/react4xp/utils/arrayUtils";
    import {PageDescriptor} from '@enonic-types/core';
    import type {Content} from '@enonic-types/lib-content';
    import type {ComponentProcessorFunction} from '@enonic-types/lib-react4xp/DataFetcher';
    
    function fetchAdditionalPhotos(photosIds) {
        return photosIds.map(photoId => {
            if (photoId) {
            const photoContent = getContentByKey<Content>({key: photoId});
            return {
                _id: photoContent._id,
                title: photoContent.displayName,
                imageUrl: imageUrl({id: photoContent._id, scale: 'block(175, 175)'})
            };
            }
        });
    }
    
    export const personProcessor: ComponentProcessorFunction<PageDescriptor>
        = (params) => {
        const photos: string[] = toArray<string>(params.content.data.photos as string | string[])
        const firstPhotoId = photos[0] || '';
        const remainingPhotoIds = photos.slice(1);
    
        let firstPhoto = null;
        if (firstPhotoId) {
        const {_id, displayName} = getContentByKey<Content>({key: firstPhotoId});
            firstPhoto = {
                _id,
                title: displayName,
                imageUrl: imageUrl({id: _id, scale: 'block(1200, 675)'})
            }
        }
    
        const extraPhotos = fetchAdditionalPhotos(remainingPhotoIds);
    
        return {
            displayName: `${params.content.displayName}`,
            photo: firstPhoto,
            birthDate: params.content.data.dateofbirth,
            restPhotos: extraPhotos,
            bio: `${params.content.data.bio}`,
            bioHtml: processHtml({
                value: params.content.data.bio as string,
                imageWidths: [200, 400, 800],
            })
        };
    };

    The ProcessHtml function (included in React4XP) outputs a format that the RichText component can use, as you will see below:

  2. Update the Person component with the following content:

    /src/main/resources/react4xp/components/content/Person.tsx
    import {componentRegistry} from '/react4xp/componentRegistry';
    import {RichText} from '@enonic/react-components';
    import React from 'react'
    import styles from './Person.module.css';
    
    export const Person = (props) => {
        const {displayName, photo, restPhotos, bioHtml, birthDate} = props as any;
        return (
            <div className={styles.person}>
                <h1>{displayName}</h1>
                <p>{birthDate}</p>
                <div>
                    {
                        photo ? (
                            <>
                                <div className={styles.photos}>
                                    <img src={photo.imageUrl}
                                         title={photo.title}
                                         alt={photo.title}
                                         height={675}
                                         width={1200}
                                         loading="eager"
                                    />
                                </div>
                            </>
                        ) : (
                            <p>No photo available</p>
                        )
                    }
                    {restPhotos && restPhotos.length > 0 && (
                        <>
                            <h2>Photos</h2>
                            <div className={styles.photoContainer}>
                                <div className={styles.photoGrid}>
                                    <div className={styles.photoScroll}>
                                        {restPhotos.map((photo, index) => (
                                            <img key={index}
                                                 src={photo.imageUrl}
                                                 title={photo.title}
                                                 alt={photo.title}
                                                 height={175}
                                                 width={175}
                                            />
                                        ))}
                                    </div>
                                </div>
                            </div>
                        </>
                    )}
                </div>
                {bioHtml && (
                    <>
                        <h2>Bio</h2>
                        <div className={styles.richText}>
                            <RichText
                                data={bioHtml}
                                componentRegistry={componentRegistry}
                                loading="lazy"
                            />
                        </div>
                    </>
                )}
            </div>
        )
    }

Et voilà! And just like that we have a fully functional rich text rendering. The page should now look something like this:

rich text result
Figure 2. Person content rendered with rich text

If you have a sharp eye, you might notice there is something missing, the factbox is not rendering properly!

Macro components

The HMDB dataset defines a Fact-Box macro, which editors may use inside their rich-text fields.

  1. Add the following files to your app:

/src/main/resources/react4xp/components/macro/FactBox.tsx
import {MacroComponentParams} from '@enonic/react-components';
import React from 'react';
import styles from './FactBox.module.css';


export const Factbox = ({config, children}: MacroComponentParams) => {

    return (
        <ins className={styles.factbox}>
            <i>💬</i>
            <strong className={styles.header}>{config.header ? config.header as string : "Fact Box"}</strong>
            <div className={styles.bodyContent}> {children}</div>
        </ins>
    );
};

+ ./src/main/resources/react4xp/components/macro/FactBox.module.css

.factbox {
  display: block;
  text-decoration: none;
  border-radius: 8px;
  clear: both;
  padding: 10px 20px;
  max-width: fit-content;
  margin: 1rem;
  position: relative;
  background-color: #0b0425;

  i {
    font-style: normal;
  }
}

.factbox .header {
  font-size: 20px;
  line-height: 24px;
  margin: 0 0 0 1rem;
}

+ . Register the component

+ ./src/main/resources/react4xp/componentRegistry.tsx

import {ComponentRegistry} from '@enonic/react-components';
import {Person} from './components/content/Person';
import {Hello} from './components/hello/Hello';
import {Factbox} from './components/macro/FactBox';

export const componentRegistry = new ComponentRegistry();

componentRegistry.addContentType('portal:site', {View: Hello});
componentRegistry.addContentType('com.enonic.app.hmdb:person', {View: Person});
componentRegistry.addMacro('factbox', {View: Factbox});

Factbox complete!

You should now see the Factbox rendering properly

rich text with macro
Figure 3. Person content rendered with rich text and macro

What happened to the dataProcessor?

TODO

The raw Macro data are actually

Sweet! next up are pages.


Contents

Contents

AI-powered search

Juke AI