280 lines
8.0 KiB
JavaScript
Raw Normal View History

2018-03-05 14:30:28 +01:00
/**
*
* Wysiwyg
*
*/
import React from 'react';
import {
2018-03-08 15:50:28 +01:00
// CompositeDecorator,
ContentState,
convertFromHTML,
// convertToRaw,
Editor,
EditorState,
getDefaultKeyBinding,
2018-03-08 18:55:49 +01:00
Modifier,
RichUtils,
} from 'draft-js';
2018-03-05 14:30:28 +01:00
import PropTypes from 'prop-types';
2018-03-05 19:23:06 +01:00
import { isEmpty } from 'lodash';
2018-03-05 14:30:28 +01:00
import cn from 'classnames';
2018-03-06 18:20:05 +01:00
import Controls from 'components/WysiwygInlineControls';
import Select from 'components/InputSelect';
import WysiwygBottomControls from 'components/WysiwygBottomControls';
2018-03-05 14:30:28 +01:00
import styles from './styles.scss';
2018-03-08 18:55:49 +01:00
const SELECT_OPTIONS = [
{ id: 'Add a title', value: '' },
{ id: 'Title H1', value: '# ' },
{ id: 'Title H2', value: '## ' },
{ id: 'Title H3', value: '### ' },
{ id: 'Title H4', value: '#### '},
{ id: 'Title H5', value: '##### ' },
{ id: 'Title H6', value: '###### ' },
];
2018-03-08 18:55:49 +01:00
2018-03-12 12:05:29 +01:00
// NOTE: I leave that as a reminder
const CONTROLS = [
2018-03-08 14:24:26 +01:00
[
{label: 'B', style: 'BOLD', handler: 'toggleInlineStyle' },
{label: 'I', style: 'ITALIC', className: 'styleButtonItalic', handler: 'toggleInlineStyle' },
{label: 'U', style: 'UNDERLINE', handler: 'toggleInlineStyle' },
{label: 'UL', style: 'unordered-list-item', className: 'styleButtonUL', hideLabel: true, handler: 'toggleBlockType' },
{label: 'OL', style: 'ordered-list-item', className: 'styleButtonOL', hideLabel: true, handler: 'toggleBlockType' },
],
[
2018-03-08 14:24:26 +01:00
{label: '<>', style: 'code-block', handler: 'toggleBlockType' },
{label: 'quotes', style: 'blockquote', className: 'styleButtonBlockQuote', hideLabel: true, handler: 'toggleBlockType' },
],
];
2018-03-06 21:32:48 +01:00
2018-03-12 12:05:29 +01:00
const NEW_CONTROLS = [
[
{label: 'B', style: 'BOLD', handler: 'addEntity', text: '__text in bold__' },
{label: 'I', style: 'ITALIC', className: 'styleButtonItalic', handler: 'addEntity', text: '*text in italic*' },
{label: 'U', style: 'UNDERLINE', handler: 'addEntity', text: '<u>underlined text</u>' },
{label: 'UL', style: 'unordered-list-item', className: 'styleButtonUL', hideLabel: true, handler: 'addEntity', text: '-' },
{label: 'OL', style: 'ordered-list-item', className: 'styleButtonOL', hideLabel: true, handler: 'addEntity', text: '1.' },
],
];
2018-03-06 18:20:05 +01:00
function getBlockStyle(block) {
switch (block.getType()) {
case 'blockquote':
return styles.editorBlockquote;
case 'code-block':
return styles.editorCodeBlock;
2018-03-06 18:20:05 +01:00
default: return null;
}
}
2018-03-06 18:20:05 +01:00
/* eslint-disable react/no-string-refs */
/* eslint-disable react/jsx-handler-names */
2018-03-05 14:30:28 +01:00
class Wysiwyg extends React.Component {
2018-03-06 18:20:05 +01:00
constructor(props) {
super(props);
2018-03-08 15:50:28 +01:00
this.state = {
editorState: EditorState.createEmpty(),
isFocused: false,
initialValue: '',
previewHTML: false,
headerValue: '',
};
2018-03-06 18:20:05 +01:00
this.focus = () => {
this.setState({ isFocused: true });
return this.domEditor.focus();
};
2018-03-06 18:20:05 +01:00
}
2018-03-05 14:30:28 +01:00
componentDidMount() {
if (this.props.autoFocus) {
this.focus();
}
2018-03-05 19:23:06 +01:00
if (!isEmpty(this.props.value)) {
this.setInitialValue(this.props);
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.props.value && !this.state.hasInitialValue) {
this.setInitialValue(nextProps);
}
2018-03-06 21:32:48 +01:00
// Handle reset props
if (nextProps.value === this.state.initialValue && this.state.hasInitialValue) {
this.setInitialValue(nextProps);
}
}
2018-03-08 18:55:49 +01:00
addEntity = (text) => {
2018-03-12 12:05:29 +01:00
console.log('text', text)
2018-03-08 18:55:49 +01:00
const editorState = this.state.editorState;
const currentContent = editorState.getCurrentContent();
// Get the selected text
const selection = editorState.getSelection();
const anchorKey = selection.getAnchorKey();
const currentContentBlock = currentContent.getBlockForKey(anchorKey);
const start = selection.getStartOffset();
const end = selection.getEndOffset();
const selectedText = currentContentBlock.getText().slice(start, end);
// Replace it with the value
const textWithEntity = Modifier.replaceText(currentContent, selection, `${text} ${selectedText}`);
this.setState({
editorState: EditorState.push(editorState, textWithEntity, 'insert-characters'),
headerValue: '',
2018-03-08 18:55:49 +01:00
}, () => {
this.focus();
2018-03-08 18:55:49 +01:00
});
}
handleChangeSelect = ({ target }) => {
this.setState({ headerValue: target.value });
this.addEntity(target.value);
}
2018-03-08 14:24:26 +01:00
2018-03-05 19:23:06 +01:00
onChange = (editorState) => {
2018-03-06 18:20:05 +01:00
this.setState({ editorState });
this.props.onChange({ target: {
2018-03-05 19:23:06 +01:00
value: editorState.getCurrentContent().getPlainText(),
2018-03-06 18:20:05 +01:00
name: this.props.name,
type: 'textarea',
}});
}
2018-03-05 19:23:06 +01:00
2018-03-06 18:20:05 +01:00
mapKeyToEditorCommand = (e) => {
if (e.keyCode === 9 /* TAB */) {
const newEditorState = RichUtils.onTab(
e,
this.state.editorState,
4, /* maxDepth */
);
if (newEditorState !== this.state.editorState) {
this.onChange(newEditorState);
}
return;
}
return getDefaultKeyBinding(e);
}
toggleBlockType = (blockType) => {
this.onChange(
RichUtils.toggleBlockType(
this.state.editorState,
blockType
)
);
2018-03-05 19:23:06 +01:00
}
2018-03-05 14:30:28 +01:00
2018-03-06 18:20:05 +01:00
toggleInlineStyle = (inlineStyle) => {
this.onChange(
RichUtils.toggleInlineStyle(
this.state.editorState,
inlineStyle
)
);
}
2018-03-05 14:30:28 +01:00
2018-03-05 19:23:06 +01:00
setInitialValue = (props) => {
2018-03-06 18:20:05 +01:00
const contentState = ContentState.createFromText(props.value);
let editorState = EditorState.createWithContent(contentState);
2018-03-05 19:23:06 +01:00
// Get the cursor at the end
editorState = EditorState.moveFocusToEnd(editorState);
2018-03-06 21:32:48 +01:00
this.setState({ editorState, hasInitialValue: true, initialValue: props.value });
2018-03-05 19:23:06 +01:00
}
handleKeyCommand(command, editorState) {
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
this.onChange(newState);
return true;
}
return false;
}
componentDidCatch(error, info) {
console.log('err', error);
console.log('info', info);
}
2018-03-06 18:20:05 +01:00
previewHTML = () => {
const blocksFromHTML = convertFromHTML(this.props.value);
const contentState = ContentState.createFromBlockArray(blocksFromHTML);
2018-03-06 21:32:48 +01:00
return EditorState.createWithContent(contentState);
2018-03-06 18:20:05 +01:00
}
2018-03-05 14:30:28 +01:00
render() {
const { editorState } = this.state;
return (
2018-03-06 18:20:05 +01:00
<div className={cn(styles.editorWrapper, this.state.isFocused && styles.editorFocus)}>
<div className={styles.controlsContainer}>
2018-03-08 18:55:49 +01:00
<div style={{ minWidth: '161px', marginLeft: '8px' }}>
<Select
name="headerSelect"
onChange={this.handleChangeSelect}
value={this.state.headerValue}
selectOptions={SELECT_OPTIONS}
/>
</div>
2018-03-12 12:05:29 +01:00
{NEW_CONTROLS.map((value, key) => (
<Controls
key={key}
buttons={value}
editorState={editorState}
2018-03-08 14:24:26 +01:00
handlers={{
2018-03-12 12:05:29 +01:00
addEntity: this.addEntity,
2018-03-08 14:24:26 +01:00
toggleBlockType: this.toggleBlockType,
toggleInlineStyle: this.toggleInlineStyle,
}}
onToggle={this.toggleInlineStyle}
onToggleBlock={this.toggleBlockType}
previewHTML={() => this.setState(prevState => ({ previewHTML: !prevState.previewHTML }))}
/>
))}
</div>
2018-03-06 18:20:05 +01:00
<div className={styles.editor} onClick={this.focus}>
<Editor
blockStyleFn={getBlockStyle}
editorState={editorState}
handleKeyCommand={this.handleKeyCommand}
keyBindingFn={this.mapKeyToEditorCommand}
onBlur={() => this.setState({ isFocused: false })}
onChange={this.onChange}
placeholder={this.props.placeholder}
ref={(editor) => this.domEditor = editor}
spellCheck
2018-03-06 18:20:05 +01:00
/>
<input className={styles.editorInput} value="" tabIndex="-1" />
</div>
<WysiwygBottomControls />
2018-03-06 18:20:05 +01:00
</div>
2018-03-05 14:30:28 +01:00
);
}
}
Wysiwyg.defaultProps = {
2018-03-05 19:23:06 +01:00
autoFocus: false,
onChange: () => {},
2018-03-06 18:20:05 +01:00
placeholder: '',
value: '',
2018-03-05 14:30:28 +01:00
};
Wysiwyg.propTypes = {
autoFocus: PropTypes.bool,
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
2018-03-05 14:30:28 +01:00
placeholder: PropTypes.string,
value: PropTypes.string,
2018-03-05 14:30:28 +01:00
};
export default Wysiwyg;