Skip to content

Commit 8b11060

Browse files
committed
Refactor RichText core and Replace dangerouslySetInnerHTML with RichText component in samples and documentation
1 parent 79bb752 commit 8b11060

File tree

7 files changed

+183
-129
lines changed

7 files changed

+183
-129
lines changed

docs/6-rendering-react.md

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ In this page you will learn how to create a React component for your content typ
77
Open the `src/app/components/Article.tsx` file and add the following
88

99
```tsx
10+
import { RichText } from '@episerver/cms-sdk/react/richText';
11+
1012
type Props = {
1113
opti: Infer<typeof ArticleContentType>;
1214
};
@@ -15,19 +17,60 @@ export default function Article({ opti }: Props) {
1517
return (
1618
<main>
1719
<h1>{opti.heading}</h1>
18-
<div dangerouslySetInnerHTML={{ __html: opti.body?.html ?? '' }} />
20+
<RichText content={opti.body?.json} />
1921
</main>
2022
);
2123
}
2224
```
2325

24-
> [!WARNING]
25-
> Using [React `dangerouslySetInnerHTML`](https://legacy.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml) can introduce XSS risks. In production environments you should sanitize the HTML or use `opti.body?.json` (which gives the content as JSON)
26+
> [!TIP]
27+
> Using the `<RichText/>` component is the recommended way to render rich text content. It's safer than `dangerouslySetInnerHTML` as it doesn't rely on HTML parsing, and allows you to customize how elements are rendered with your own React components.
28+
29+
### Customizing Rich Text Elements
30+
31+
You can customize how specific elements are rendered by providing custom components:
32+
33+
```tsx
34+
import { RichText, type ElementProps } from '@episerver/cms-sdk/react/richText';
35+
36+
// Custom heading renderer
37+
const CustomHeading = (props: ElementProps) => (
38+
<h2 style={{ color: 'blue', fontSize: '1.8rem' }}>{props.children}</h2>
39+
);
40+
41+
// Custom link renderer
42+
const CustomLink = (props: ElementProps) => (
43+
<a
44+
href={props.element.url}
45+
style={{ color: 'purple', textDecoration: 'underline' }}
46+
target="_blank"
47+
rel="noopener noreferrer"
48+
>
49+
{props.children}
50+
</a>
51+
);
52+
53+
export default function Article({ opti }: Props) {
54+
return (
55+
<main>
56+
<h1>{opti.heading}</h1>
57+
<RichText
58+
content={opti.body?.json}
59+
elements={{
60+
'heading-two': CustomHeading,
61+
link: CustomLink,
62+
}}
63+
/>
64+
</main>
65+
);
66+
}
67+
```
2668

2769
The entire file should look like this:
2870

2971
```tsx
3072
import { contentType, Infer } from '@episerver/cms-sdk';
73+
import { RichText } from '@episerver/cms-sdk/react/richText';
3174

3275
export const ArticleContentType = contentType({
3376
key: 'Article',
@@ -50,7 +93,7 @@ export default function Article({ opti }: Props) {
5093
return (
5194
<main>
5295
<h1>{opti.heading}</h1>
53-
<div dangerouslySetInnerHTML={{ __html: opti.body?.html ?? '' }} />
96+
<RichText content={opti.body?.json} />
5497
</main>
5598
);
5699
}

packages/optimizely-cms-sdk/src/components/richText/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ export type {
99
TextWithMark,
1010
RendererConfig,
1111
HtmlComponentConfig,
12+
BaseElementRendererProps,
13+
BaseLeafRendererProps,
14+
BaseElementMap,
15+
BaseLeafMap,
16+
RichTextPropsBase,
1217
} from './renderer.js';
1318
export { isText, isElement } from './renderer.js';
1419
export type { RenderNode } from './renderer.js';

packages/optimizely-cms-sdk/src/components/richText/renderer.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,86 @@ export type TextWithMark<T extends MarkType> = Text & {
6363
[K in T]: true;
6464
};
6565

66+
/**
67+
* Props for element renderer components (framework-agnostic)
68+
*/
69+
export interface BaseElementRendererProps {
70+
element: Element;
71+
attributes?: Record<string, unknown>;
72+
text?: string; // Enhanced API - direct text access (optional for backward compatibility)
73+
}
74+
75+
/**
76+
* Props for leaf renderer components (framework-agnostic)
77+
*/
78+
export interface BaseLeafRendererProps {
79+
leaf: Text;
80+
attributes?: Record<string, unknown>;
81+
text?: string; // Enhanced API - direct text access (optional for backward compatibility)
82+
}
83+
84+
/**
85+
* Generic mapping from element types to renderer components
86+
* Can be specialized for each framework: ElementMap<ReactComponent>, ElementMap<VueComponent>, etc.
87+
*/
88+
export type BaseElementMap<TRenderer = unknown> = {
89+
[K in ElementType]?: TRenderer;
90+
} & {
91+
[key: string]: TRenderer;
92+
};
93+
94+
/**
95+
* Generic mapping from leaf mark types to renderer components
96+
* Can be specialized for each framework: LeafMap<ReactComponent>, LeafMap<VueComponent>, etc.
97+
*/
98+
export type BaseLeafMap<TRenderer = unknown> = {
99+
[K in MarkType]?: TRenderer;
100+
} & {
101+
[key: string]: TRenderer;
102+
};
103+
104+
/**
105+
* Generic props for RichText component (framework-agnostic)
106+
* Can be specialized for each framework with specific renderer types
107+
*/
108+
export interface RichTextPropsBase<
109+
TElementRenderer = unknown,
110+
TLeafRenderer = unknown
111+
> {
112+
/**
113+
* Slate.js compatible JSON content to render
114+
*/
115+
content?: {
116+
type: 'richText';
117+
children: Node[];
118+
};
119+
120+
/**
121+
* Custom components for rendering elements by type
122+
*/
123+
elements?: BaseElementMap<TElementRenderer>;
124+
125+
/**
126+
* Custom components for rendering text marks
127+
*/
128+
leafs?: BaseLeafMap<TLeafRenderer>;
129+
130+
/**
131+
* Fallback element type when no custom renderer is found
132+
*/
133+
elementFallback?: string;
134+
135+
/**
136+
* Fallback leaf element type when no custom renderer is found
137+
*/
138+
leafFallback?: string;
139+
140+
/**
141+
* Whether to decode HTML entities in text content
142+
*/
143+
decodeHtmlEntities?: boolean;
144+
}
145+
66146
/**
67147
* Available element types in the default implementation
68148
*/

packages/optimizely-cms-sdk/src/react/richText/index.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,17 @@ export {
55
createHtmlComponent,
66
createLeafComponent,
77
} from './lib.js';
8+
89
export type {
9-
ElementRendererProps,
10-
LeafRendererProps,
10+
ElementProps,
11+
LeafProps,
12+
ElementRenderer,
13+
LeafRenderer,
1114
ElementMap,
1215
LeafMap,
16+
RichTextProps,
1317
} from './lib.js';
1418

15-
// Class-based renderer (alternative approach)
16-
export { ReactRichTextRenderer, createReactRenderer } from './renderer.js';
17-
export type { ReactRendererConfig } from './renderer.js';
18-
19-
// Re-export core types that React users might need
2019
export type {
2120
Node,
2221
Element,

packages/optimizely-cms-sdk/src/react/richText/lib.ts

Lines changed: 23 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,35 @@ import type { PropsWithChildren, JSX } from 'react';
33
import {
44
defaultElementTypeMap,
55
defaultMarkTypeMap,
6-
} from '../../components/richText/renderer.js';
7-
import type {
8-
Node,
9-
Text,
10-
Element,
11-
ElementType,
12-
MarkType,
6+
type BaseElementRendererProps,
7+
type BaseLeafRendererProps,
8+
type BaseElementMap,
9+
type BaseLeafMap,
10+
type HtmlComponentConfig,
11+
type RichTextPropsBase,
1312
} from '../../components/richText/renderer.js';
1413

1514
/**
16-
* Props for React element renderer components
15+
* React-specific element renderer props (extends shared props with React children)
1716
*/
18-
export interface ElementRendererProps extends PropsWithChildren {
19-
element: Element;
20-
attributes?: Record<string, unknown>;
21-
text?: string; // Enhanced API - direct text access (optional for backward compatibility)
22-
}
17+
export interface ElementRendererProps
18+
extends BaseElementRendererProps,
19+
PropsWithChildren {}
2320

2421
/**
25-
* Better named alias for ElementRendererProps - more intuitive and developer-friendly
26-
* Use this for new code: ({ text, attributes, element }: ElementProps) => JSX.Element
22+
* Prop type used for custom Element components
2723
*/
2824
export type ElementProps = ElementRendererProps;
2925

3026
/**
31-
* Props for React leaf renderer components
27+
* React-specific leaf renderer props (extends shared props with React children)
3228
*/
33-
export interface LeafRendererProps extends PropsWithChildren {
34-
leaf: Text;
35-
attributes?: Record<string, unknown>;
36-
text?: string; // Enhanced API - direct text access (optional for backward compatibility)
37-
}
29+
export interface LeafRendererProps
30+
extends BaseLeafRendererProps,
31+
PropsWithChildren {}
3832

3933
/**
40-
* Better named alias for LeafRendererProps - more intuitive and developer-friendly
41-
* Use this for new code: ({ text, attributes, leaf }: LeafProps) => JSX.Element
34+
* Prop type used for custom Leaf components
4235
*/
4336
export type LeafProps = LeafRendererProps;
4437

@@ -47,105 +40,26 @@ export type LeafProps = LeafRendererProps;
4740
*/
4841
export type ElementRenderer = React.ComponentType<ElementRendererProps>;
4942

50-
/**
51-
* Better named alias for ElementRenderer - more intuitive and developer-friendly
52-
* Use this for new code: const MyComponent: ElementComponent = ({ text, attributes }) => JSX.Element
53-
*/
54-
export type ElementComponent = ElementRenderer;
55-
5643
/**
5744
* React component for rendering Slate text leaves
5845
*/
5946
export type LeafRenderer = React.ComponentType<LeafRendererProps>;
6047

6148
/**
62-
* Better named alias for LeafRenderer - more intuitive and developer-friendly
63-
* Use this for new code: const MyComponent: LeafComponent = ({ text, attributes }) => JSX.Element
49+
* React-specific mapping types (specializes generic types with React components)
6450
*/
65-
export type LeafComponent = LeafRenderer;
51+
export type ElementMap = BaseElementMap<ElementRenderer>;
6652

6753
/**
68-
* Mapping from element types to React renderer components
69-
* Provides IntelliSense for known element types while allowing custom types
54+
* React-specific mapping types (specializes generic types with React components)
7055
*/
71-
export type ElementMap = {
72-
[K in ElementType]?: ElementRenderer;
73-
} & {
74-
[key: string]: ElementRenderer;
75-
};
56+
export type LeafMap = BaseLeafMap<LeafRenderer>;
7657

7758
/**
78-
* Mapping from leaf mark types to React renderer components
79-
* Provides IntelliSense for known mark types while allowing custom types
59+
* React-specific RichText props
8060
*/
81-
export type LeafMap = {
82-
[K in MarkType]?: LeafRenderer;
83-
} & {
84-
[key: string]: LeafRenderer;
85-
};
86-
87-
/**
88-
* Props for the main React RichText component
89-
*/
90-
export interface RichTextProps {
91-
/**
92-
* Slate.js compatible JSON content to render
93-
*/
94-
content?: {
95-
type: 'richText';
96-
children: Node[];
97-
};
98-
99-
/**
100-
* Custom React components for rendering elements by type
101-
*/
102-
elements?: ElementMap;
103-
104-
/**
105-
* Custom React components for rendering text marks
106-
*/
107-
leafs?: LeafMap;
108-
109-
/**
110-
* Fallback element type when no custom renderer is found
111-
*/
112-
elementFallback?: string;
113-
114-
/**
115-
* Fallback leaf element type when no custom renderer is found
116-
*/
117-
leafFallback?: string;
118-
119-
/**
120-
* Whether to decode HTML entities in text content
121-
*/
122-
decodeHtmlEntities?: boolean;
123-
}
124-
125-
/**
126-
* Utility type for Slate.js content arrays
127-
*/
128-
export type Content = Node[];
129-
130-
/**
131-
* Configuration for HTML component creation
132-
*/
133-
export interface HtmlComponentConfig {
134-
/**
135-
* Whether the element is self-closing (like <img>, <br>)
136-
*/
137-
selfClosing?: boolean;
138-
139-
/**
140-
* Default CSS class to apply
141-
*/
142-
className?: string;
143-
144-
/**
145-
* Additional HTML attributes to apply
146-
*/
147-
attributes?: Record<string, unknown>;
148-
}
61+
export interface RichTextProps
62+
extends RichTextPropsBase<ElementRenderer, LeafRenderer> {}
14963

15064
/**
15165
* Converts framework-agnostic attributes to React props

0 commit comments

Comments
 (0)