mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-09 10:11:12 +00:00
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:
parent
5e9e9381e9
commit
3ebc1933cc
6 changed files with 190 additions and 12 deletions
|
|
@ -21,6 +21,7 @@ const SHOW_ADDITIONAL_ACTIONS = [
|
|||
'DropdownV2',
|
||||
'MultiselectV2',
|
||||
'Button',
|
||||
'RichTextEditor',
|
||||
];
|
||||
const PROPERTIES_VS_ACCORDION_TITLE = {
|
||||
Text: 'Data',
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue