Add controls

This commit is contained in:
cyril lopez 2018-03-06 18:20:05 +01:00
parent abc4598aa7
commit 19c5e46f69
4 changed files with 264 additions and 53 deletions

View File

@ -5,16 +5,39 @@
*/
import React from 'react';
import { ContentState, convertFromHTML, Editor, EditorState } from 'draft-js';
import { ContentState, convertFromHTML, Editor, EditorState, getDefaultKeyBinding, RichUtils, convertToRaw } from 'draft-js';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { isEmpty } from 'lodash';
import cn from 'classnames';
import Controls from 'components/WysiwygInlineControls';
import styles from './styles.scss';
const styleMap = {
CODE: {
backgroundColor: 'rgba(0, 0, 0, 0.05)',
fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
fontSize: 16,
padding: 2,
},
};
function getBlockStyle(block) {
switch (block.getType()) {
case 'blockquote': return 'RichEditor-blockquote';
default: return null;
}
}
class Wysiwyg extends React.Component {
state = { editorState: EditorState.createEmpty(), isFocused: false, hasInitialValue: false };
constructor(props) {
super(props);
this.state = { editorState: EditorState.createEmpty(), isFocused: false };
this.focus = () => {
this.setState({ isFocused: true });
return this.refs.editor.focus();
}
}
componentDidMount() {
if (this.props.autoFocus) {
@ -32,40 +55,60 @@ class Wysiwyg extends React.Component {
}
}
focus = () => {
this.setState({ isFocused: true });
this.domEditor.focus();
handleKeyCommand(command, editorState) {
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
this.onChange(newState);
return true;
}
return false;
}
handleBlur = (editorState) => {
this.setState({ isFocused: false });
this.domEditor.blur();
}
/**
* Determine if the state is empty
* @return {Boolean}
*/
isContentStateEmpty = () => !this.state.editorState.getCurrentContent().hasText();
onChange = (editorState) => {
const target = {
name: this.props.name,
type: 'text',
this.setState({ editorState });
this.props.onChange({ target: {
value: editorState.getCurrentContent().getPlainText(),
};
this.props.onChange({ target });
this.setState({editorState});
name: this.props.name,
type: 'textarea',
}});
}
setDomEditorRef = ref => this.domEditor = ref;
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
)
);
}
toggleInlineStyle = (inlineStyle) => {
this.onChange(
RichUtils.toggleInlineStyle(
this.state.editorState,
inlineStyle
)
);
}
setInitialValue = (props) => {
let editorState;
const blocksFromHTML = convertFromHTML(props.value);
const contentState = ContentState.createFromBlockArray(blocksFromHTML);
editorState = EditorState.createWithContent(contentState);
const contentState = ContentState.createFromText(props.value);
let editorState = EditorState.createWithContent(contentState);
// Get the cursor at the end
editorState = EditorState.moveFocusToEnd(editorState);
@ -73,40 +116,47 @@ class Wysiwyg extends React.Component {
this.setState({ editorState, hasInitialValue: true });
}
previewHTML = () => {
const blocksFromHTML = convertFromHTML(this.props.value);
const contentState = ContentState.createFromBlockArray(blocksFromHTML);
const editorState = EditorState.createWithContent(contentState);
this.setState({ editorState });
}
render() {
const {
placeholder,
} = this.props;
const { editorState } = this.state;
return (
<React.Fragment>
<div className={cn(styles.editorWrapper, this.state.isFocused && styles.editorFocus)} onClick={this.focus}>
<FormattedMessage id={placeholder} defaultMessage={placeholder}>
{(message) => (
<Editor
editorState={editorState}
onChange={this.onChange}
onBlur={this.handleBlur}
ref={this.setDomEditorRef}
/>
)}
</FormattedMessage>
</div>
<input
className={styles.editorInput}
type="button"
value=""
tabIndex="-1"
<div className={cn(styles.editorWrapper, this.state.isFocused && styles.editorFocus)}>
<Controls
editorState={editorState}
onToggle={this.toggleInlineStyle}
onToggleBlock={this.toggleBlockType}
previewHTML={this.previewHTML}
/>
</React.Fragment>
<div className={styles.editor} onClick={this.focus}>
<Editor
blockStyleFn={getBlockStyle}
customStyleMap={styleMap}
editorState={editorState}
handleKeyCommand={this.handleKeyCommand}
keyBindingFn={this.mapKeyToEditorCommand}
onBlur={() => this.setState({ isFocused: false })}
onChange={this.onChange}
placeholder={this.props.placeholder}
ref="editor"
spellCheck={true}
/>
<input className={styles.editorInput} value="" tabIndex="-1" />
</div>
</div>
);
}
}
Wysiwyg.defaultProps = {
autoFocus: false,
placeholder: 'app.utils.placeholder.defaultMessage',
placeholder: '',
};
Wysiwyg.propTypes = {

View File

@ -1,7 +1,6 @@
.editorWrapper {
min-height: 350px;
margin-top: .9rem;
padding: 8px 10px 0 10px;
border: 1px solid #E3E9F3;
border-radius: 0.25rem;
line-height: 18px;
@ -9,8 +8,16 @@
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
}
.editor {
min-height: 300px;
padding: 8px 10px 0 10px;
font-size: 16px;
margin-top: 10px;
cursor: text;
}
.editorFocus {
border-color: #78caff;
border-color: #78caff !important;
}
.editorInput {

View File

@ -0,0 +1,102 @@
/**
*
* WysiwygInlineControls
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './styles.scss';
const CONTROLS = [
{label: 'B', style: 'BOLD'},
{label: 'I', style: 'ITALIC', className: 'styleButtonItalic'},
{label: 'U', style: 'UNDERLINE'},
{label: 'UL', style: 'unordered-list-item'},
{label: 'OL', style: 'ordered-list-item'},
];
class StyleButton extends React.Component {
handleClick = (e) => {
e.preventDefault();
if (['UL', 'OL'].includes(this.props.label)) {
return this.props.onToggleBlock(this.props.style);
}
return this.props.onToggle(this.props.style);
}
render() {
return (
<div
className={cn(
this.props.active && styles.styleButtonActive,
styles.styleButton,
this.props.className && styles[this.props.className],
)}
onMouseDown={this.handleClick}
>
{this.props.label}
</div>
);
}
}
const WysiwygInlineControls = ({ editorState, onToggle, onToggleBlock, previewHTML }) => {
const selection = editorState.getSelection();
const blockType = editorState
.getCurrentContent()
.getBlockForKey(selection.getStartKey())
.getType();
const currentStyle = editorState.getCurrentInlineStyle();
return (
<div className={cn(styles.wysiwygInlineControls)}>
{CONTROLS.map(type => (
<StyleButton
key={type.label}
active={type.style === blockType || currentStyle.has(type.style)}
className={type.className}
label={type.label}
onToggle={onToggle}
onToggleBlock={onToggleBlock}
style={type.style}
/>
))}
<StyleButton
label={'TOGGLE'}
onToggle={previewHTML}
/>
</div>
);
}
StyleButton.defaultProps = {
active: false,
className: '',
label: '',
onToggle: () => {},
style: '',
};
StyleButton.propTypes = {
active: PropTypes.bool,
className: PropTypes.string,
label: PropTypes.string,
onToggle: PropTypes.func,
style: PropTypes.string
};
WysiwygInlineControls.defaultProps = {
onToggle: () => {},
};
WysiwygInlineControls.propTypes = {
editorState: PropTypes.object.isRequired,
onToggle: PropTypes.func,
};
export default WysiwygInlineControls;

View File

@ -0,0 +1,52 @@
.controlsWrapper {
display: flex;
user-select: none;
> div:nth-child(even) {
border-left: 0;
border-right: 0;
}
}
.styleButton {
height: 31px;
min-width: 31px;
background-color: #FFFFFF;
border: 1px solid rgba(16,22,34,0.10);
font-size: 13px;
font-weight: 500;
line-height: 31px;
text-align: center;
// color: #999;
cursor: pointer;
}
.styleButtonActive {
border: 0;
background: rgba(16,22,34,0.00);
box-shadow: inset 0 -1px 0 0 rgba(16,22,34,0.04), inset 0 1px 0 0 rgba(16,22,34,0.04);
}
.styleButtonItalic {
font-style: italic;
}
.wysiwygInlineControls {
height: 49px;
width: 100%;
display: flex;
user-select: none;
> div:nth-child(even) {
border-left: 0;
border-right: 0;
}
padding: 8px 10px 0 10px;
background-color: #F3F4F4;
}
.wysiwygInlineControlsFocus {
border-color: #78caff;
}
.active {
background-color: red;
}