mirror of
https://github.com/strapi/strapi.git
synced 2025-09-21 14:31:16 +00:00
Add controls
This commit is contained in:
parent
abc4598aa7
commit
19c5e46f69
@ -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 = {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
@ -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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user