wix-style-react
Version:
wix-style-react
155 lines • 7.83 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import { EditorState, Editor, CompositeDecorator } from 'draft-js';
import { convertFromHTML } from 'draft-convert';
import { st, classes, vars } from './RichTextInputArea.st.css';
import RichTextToolbar from './Toolbar/RichTextToolbar';
import EditorUtilities from './EditorUtilities';
import { RichTextInputAreaContext } from './RichTextInputAreaContext';
import { defaultTexts } from './RichTextInputAreaTexts';
import StatusIndicator from '../StatusIndicator';
import { StatusContext, getStatusFromContext, } from '../FormField/StatusContext';
import { WixStyleReactContext } from '../WixStyleReactProvider/context';
const decorator = new CompositeDecorator([
{
strategy: EditorUtilities.findLinkEntities,
component: ({ contentState, entityKey, children }) => {
const { url } = contentState.getEntity(entityKey).getData();
return (React.createElement("a", { "data-hook": "richtextarea-link", href: url, className: classes.link, target: "_blank",
// Avoids a potentially serious vulnerability for '_blank' links
rel: "noopener noreferrer" }, children));
},
},
]);
class RichTextInputArea extends React.PureComponent {
constructor(props) {
super(props);
this._setEditorState = (newEditorState, onStateChanged = () => { }) => {
this.setState({ editorState: newEditorState }, () => {
const { onChange = () => { } } = this.props;
const htmlText = EditorUtilities.convertToHtml(newEditorState);
const plainText = newEditorState.getCurrentContent().getPlainText();
onChange(htmlText, { plainText });
onStateChanged();
});
};
this._updateContentByValue = value => {
const content = convertFromHTML({
htmlToEntity: (nodeName, node, createEntity) => {
if (nodeName === 'a') {
return createEntity('LINK', 'MUTABLE', { url: node.href });
}
},
})(value);
const updatedEditorState = EditorState.push(this.state.editorState, content);
this.setState({ editorState: updatedEditorState });
};
/** Set value to display in the editor */
this.setValue = value => {
this._updateContentByValue(value);
};
const { texts: consumerTexts } = props;
this.state = {
editorState: EditorState.createEmpty(decorator),
texts: {
toolbarButtons: {
...defaultTexts.toolbarButtons,
...consumerTexts.toolbarButtons,
},
insertionForm: {
...defaultTexts.insertionForm,
...consumerTexts.insertionForm,
},
},
};
}
componentDidMount() {
const { initialValue } = this.props;
// TODO: currently it treats the value as an initial value
this._updateContentByValue(initialValue);
this.editorRef = React.createRef();
}
render() {
const { dataHook, className, placeholder, disabled, minHeight, maxHeight, status, statusMessage, spellCheck, } = this.props;
const isEditorEmpty = EditorUtilities.isEditorEmpty(this.state.editorState);
const finalStatus = getStatusFromContext(this.context, status);
return (React.createElement(WixStyleReactContext.Consumer, null, ({ newColorsBranding }) => (React.createElement("div", { "data-hook": dataHook, className: st(classes.root, {
hidePlaceholder: !isEditorEmpty,
disabled,
hasError: !disabled && finalStatus === 'error',
hasWarning: !disabled && finalStatus === 'warning',
newColorsBranding,
}, className),
// Using CSS variable instead of applying minHeight & maxHeight on each child, down to the editor's content
style: {
[vars.minHeight]: minHeight,
[vars.maxHeight]: maxHeight,
} },
React.createElement(RichTextInputAreaContext.Provider, { value: {
texts: this.state.texts,
} },
React.createElement(RichTextToolbar, { dataHook: "richtextarea-toolbar", className: classes.toolbar, isDisabled: disabled, editorState: this.state.editorState, onBold: this._setEditorState, onItalic: this._setEditorState, onUnderline: this._setEditorState, onLink: newEditorState => {
this._setEditorState(newEditorState, () => this.editorRef.current.focus());
}, onBulletedList: this._setEditorState, onNumberedList: this._setEditorState })),
React.createElement("div", { className: classes.editorWrapper, onClick: () => this.editorRef.current.focus() },
React.createElement(Editor, { ref: this.editorRef, editorState: this.state.editorState, onChange: this._setEditorState, placeholder: placeholder, readOnly: disabled, spellCheck: spellCheck }),
!disabled && (status || finalStatus === 'loading') && (React.createElement("span", { className: classes.statusIndicator },
React.createElement(StatusIndicator, { dataHook: "richtextarea-status-indicator", status: finalStatus, message: statusMessage }))))))));
}
}
RichTextInputArea.contextType = StatusContext;
RichTextInputArea.displayName = 'RichTextInputArea';
RichTextInputArea.propTypes = {
/** Specifies a CSS class name to be appended to the component’s root element. */
className: PropTypes.string,
/** Applies a data-hook HTML attribute that can be used in the tests. */
dataHook: PropTypes.string,
/** Sets the initial value to be displayed in the editor. */
initialValue: PropTypes.string,
/** Sets a placeholder message to display. */
placeholder: PropTypes.string,
/** Specifies whether an editor and its toolbar should be disabled. */
disabled: PropTypes.bool,
/** Specifies the status of a field. */
status: PropTypes.oneOf(['error', 'warning', 'loading']),
/** Defines the message to display on status icon hover. If not given or empty there will be no tooltip. */
statusMessage: PropTypes.string,
/** Defines a standard callback function for changes: `onChange(htmlText, { plainText })` */
onChange: PropTypes.func,
/** Defines a minimum height for the editor (it grows by default) */
minHeight: PropTypes.string,
/** Defines a maximum height for the editor (it grows by default) */
maxHeight: PropTypes.string,
/**
* Enables browsers spell checking.
* Do not affect IE.
* In Safari, autocorrects by default.
*/
spellCheck: PropTypes.bool,
/** Defines text styles to be shown. */
texts: PropTypes.shape({
toolbarButtons: PropTypes.shape({
boldButtonLabel: PropTypes.string,
italicButtonLabel: PropTypes.string,
underlineButtonLabel: PropTypes.string,
linkButtonLabel: PropTypes.string,
bulletedListButtonLabel: PropTypes.string,
numberedListButtonLabel: PropTypes.string,
}),
insertionForm: PropTypes.shape({
confirmButtonLabel: PropTypes.string,
cancelButtonLabel: PropTypes.string,
link: PropTypes.shape({
textInputPlaceholder: PropTypes.string,
urlInputPlaceholder: PropTypes.string,
}),
}),
}),
};
RichTextInputArea.defaultProps = {
initialValue: '<p/>',
texts: {},
disabled: false,
};
export default RichTextInputArea;
//# sourceMappingURL=RichTextInputArea.js.map