Enhance: Added 'Set Value' functionality and support for HTML with hyperlinks in Rich Text Editor component (#11406)

* Added HTML support for Placeholder and Default Value property. Added CSA for loading state.

* Exposed isDisabled, isVisible and isLoading variables. Also exposed setValue, setDisable, setVisibility and setLoading functions.

* Fixed issue where loading state functionality was not visible in the properties section on the initial render of rich text editor.
This commit is contained in:
Devanshu Rastogi 2024-12-04 14:40:57 +05:30 committed by GitHub
parent 5e9e9381e9
commit 3ebc1933cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 190 additions and 12 deletions

View file

@ -21,6 +21,7 @@ const SHOW_ADDITIONAL_ACTIONS = [
'DropdownV2',
'MultiselectV2',
'Button',
'RichTextEditor',
];
const PROPERTIES_VS_ACCORDION_TITLE = {
Text: 'Data',

View file

@ -28,6 +28,15 @@ export const richtextareaConfig = {
defaultValue: 'Default text',
},
},
loadingState: {
type: 'toggle',
displayName: 'Show loading state',
validation: {
schema: { type: 'boolean' },
defaultValue: false,
},
section: 'additionalActions',
},
},
events: {},
styles: {
@ -55,6 +64,28 @@ export const richtextareaConfig = {
exposedVariables: {
value: '',
},
actions: [
{
handle: 'setValue',
displayName: 'Set value',
params: [{ handle: 'value', displayName: 'Value', defaultValue: 'New text' }],
},
{
handle: 'setDisable',
displayName: 'Set disable',
params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
},
{
handle: 'setVisibility',
displayName: 'Set visibility',
params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: `{{true}}`, type: 'toggle' }],
},
{
handle: 'setLoading',
displayName: 'Set loading',
params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
},
],
definition: {
others: {
showOnDesktop: { value: '{{true}}' },
@ -63,6 +94,7 @@ export const richtextareaConfig = {
properties: {
placeholder: { value: 'Placeholder text' },
defaultValue: { value: '' },
loadingState: { value: `{{false}}` },
},
events: [],
styles: {

View file

@ -1,8 +1,10 @@
/* eslint-disable react/no-string-refs */
import React from 'react';
import { Editor, EditorState, RichUtils, getDefaultKeyBinding, ContentState } from 'draft-js';
import { Editor, EditorState, RichUtils, getDefaultKeyBinding, ContentState, convertFromHTML } from 'draft-js';
import 'draft-js/dist/Draft.css';
import { stateToHTML } from 'draft-js-export-html';
import Loader from '@/ToolJetUI/Loader/Loader';
import DOMPurify from 'dompurify';
// Custom overrides for "code" style.
const styleMap = {
@ -148,8 +150,11 @@ const InlineStyleControls = (props) => {
class DraftEditor extends React.Component {
constructor(props) {
super(props);
const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(this.props.defaultValue));
this.state = {
editorState: EditorState.createWithContent(ContentState.createFromText(this.props.defaultValue)),
editorState: EditorState.createWithContent(
ContentState.createFromBlockArray(blocksFromHTML.contentBlocks, blocksFromHTML.entityMap)
),
};
this.editorContainerRef = React.createRef();
@ -181,6 +186,38 @@ class DraftEditor extends React.Component {
if (this.controlsRef.current) {
this.resizeObserver.observe(this.controlsRef.current);
}
const exposedVariables = {
value: this.props.defaultValue,
isDisabled: this.props.isDisabled,
isVisible: this.props.isVisible,
isLoading: this.props.isLoading,
setValue: async (text) => {
const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(text));
const newContentState = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap
);
const newEditorState = EditorState.createWithContent(newContentState);
const html = stateToHTML(newContentState);
this.props.handleChange(html);
this.setState({ editorState: newEditorState });
},
setDisable: async (value) => {
this.props.setExposedVariable('isDisabled', value);
this.props.setIsDisabled(value);
},
setVisibility: async (value) => {
this.props.setExposedVariable('isVisible', value);
this.props.setIsVisible(value);
},
setLoading: async (value) => {
this.props.setExposedVariable('isLoading', value);
this.props.setIsLoading(value);
},
};
this.props.setExposedVariables(exposedVariables);
this.props.isInitialRender.current = false;
}
componentWillUnmount() {
@ -191,7 +228,8 @@ class DraftEditor extends React.Component {
componentDidUpdate(prevProps) {
if (prevProps.defaultValue !== this.props.defaultValue) {
const newContentState = ContentState.createFromText(this.props.defaultValue);
const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(this.props.defaultValue));
const newContentState = ContentState.createFromBlockArray(blocksFromHTML.contentBlocks, blocksFromHTML.entityMap);
const newEditorState = EditorState.createWithContent(newContentState);
const html = stateToHTML(newContentState);
@ -242,7 +280,13 @@ class DraftEditor extends React.Component {
}
}
return (
return this.props.isLoading ? (
<div style={{ height: '100%', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<center>
<Loader width="16" absolute={false} />
</center>
</div>
) : (
<div className="RichEditor-root" style={{ overflowY: 'scroll', scrollbarWidth: 'none' }}>
<div className="RichEditor-controls" ref={this.controlsRef}>
<BlockStyleControls editorState={editorState} onToggle={this.toggleBlockType} />
@ -256,7 +300,13 @@ class DraftEditor extends React.Component {
handleKeyCommand={this.handleKeyCommand}
keyBindingFn={this.mapKeyToEditorCommand}
onChange={this.onChange}
placeholder={this.props.placeholder}
placeholder={
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(this.props.placeholder || ''),
}}
/>
}
ref="editor"
spellCheck={true}
/>

View file

@ -1,4 +1,5 @@
import React, { useEffect } from 'react';
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect, useRef, useState } from 'react';
import 'draft-js/dist/Draft.css';
import { DraftEditor } from './DraftEditor';
@ -8,17 +9,38 @@ export const RichTextEditor = function RichTextEditor({
properties,
styles,
setExposedVariable,
setExposedVariables,
dataCy,
}) {
const isInitialRender = useRef(true);
const { visibility, disabledState, boxShadow } = styles;
const placeholder = properties.placeholder;
const defaultValue = properties?.defaultValue ?? '';
// exposing the default value at first
const [isDisabled, setIsDisabled] = useState(disabledState);
const [isVisible, setIsVisible] = useState(visibility);
const [isLoading, setIsLoading] = useState(properties?.loadingState);
useEffect(() => {
setExposedVariable('value', defaultValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (isDisabled !== disabledState) setIsDisabled(disabledState);
if (isVisible !== visibility) setIsVisible(visibility);
if (isLoading !== properties.loadingState) setIsLoading(properties.loadingState);
}, [properties.loadingState, styles.visibility, styles.disabledState]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isDisabled', disabledState);
}, [disabledState]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isVisible', visibility);
}, [visibility]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isLoading', isLoading);
}, [isLoading]);
function handleChange(html) {
setExposedVariable('value', html);
@ -26,16 +48,25 @@ export const RichTextEditor = function RichTextEditor({
return (
<div
data-disabled={disabledState}
style={{ height: `${height}px`, display: visibility ? '' : 'none', boxShadow }}
data-disabled={isDisabled}
style={{ height: `${height}px`, display: isVisible ? '' : 'none', boxShadow }}
data-cy={dataCy}
>
<DraftEditor
isInitialRender={isInitialRender}
handleChange={handleChange}
height={height}
width={width}
placeholder={placeholder}
defaultValue={defaultValue}
isLoading={isLoading}
isVisible={visibility}
isDisabled={disabledState}
setExposedVariable={setExposedVariable}
setExposedVariables={setExposedVariables}
setIsDisabled={setIsDisabled}
setIsVisible={setIsVisible}
setIsLoading={setIsLoading}
></DraftEditor>
</div>
);

View file

@ -28,6 +28,15 @@ export const richtextareaConfig = {
defaultValue: 'Default text',
},
},
loadingState: {
type: 'toggle',
displayName: 'Show loading state',
validation: {
schema: { type: 'boolean' },
defaultValue: false,
},
section: 'additionalActions',
},
},
events: {},
styles: {
@ -55,6 +64,28 @@ export const richtextareaConfig = {
exposedVariables: {
value: '',
},
actions: [
{
handle: 'setValue',
displayName: 'Set value',
params: [{ handle: 'value', displayName: 'Value', defaultValue: 'New text' }],
},
{
handle: 'setDisable',
displayName: 'Set disable',
params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
},
{
handle: 'setVisibility',
displayName: 'Set visibility',
params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: `{{true}}`, type: 'toggle' }],
},
{
handle: 'setLoading',
displayName: 'Set loading',
params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
},
],
definition: {
others: {
showOnDesktop: { value: '{{true}}' },
@ -63,6 +94,7 @@ export const richtextareaConfig = {
properties: {
placeholder: { value: 'Placeholder text' },
defaultValue: { value: '' },
loadingState: { value: `{{false}}` },
},
events: [],
styles: {

View file

@ -28,6 +28,15 @@ export const richtextareaConfig = {
defaultValue: 'Default text',
},
},
loadingState: {
type: 'toggle',
displayName: 'Show loading state',
validation: {
schema: { type: 'boolean' },
defaultValue: false,
},
section: 'additionalActions',
},
},
events: {},
styles: {
@ -55,6 +64,28 @@ export const richtextareaConfig = {
exposedVariables: {
value: '',
},
actions: [
{
handle: 'setValue',
displayName: 'Set value',
params: [{ handle: 'value', displayName: 'Value', defaultValue: 'New text' }],
},
{
handle: 'setDisable',
displayName: 'Set disable',
params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
},
{
handle: 'setVisibility',
displayName: 'Set visibility',
params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: `{{true}}`, type: 'toggle' }],
},
{
handle: 'setLoading',
displayName: 'Set loading',
params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
},
],
definition: {
others: {
showOnDesktop: { value: '{{true}}' },
@ -63,6 +94,7 @@ export const richtextareaConfig = {
properties: {
placeholder: { value: 'Placeholder text' },
defaultValue: { value: '' },
loadingState: { value: `{{false}}` },
},
events: [],
styles: {