Last active
December 9, 2022 03:09
-
-
Save norayr93/ba84a3b4b2e2bb5de88c8f8c5e5f01c1 to your computer and use it in GitHub Desktop.
Editor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This component is for creating and editing ctaBoxes. It openes a modal. | |
import React, { Component } from 'react'; | |
import PropTypes from 'prop-types'; | |
import classNames from 'classnames'; | |
import linkifyIt from 'linkify-it'; | |
import { stopPropagation } from '../../../utils/common'; | |
import { getFirstIcon } from '../../../utils/toolbar'; | |
import Option from '../../../components/Option'; | |
import { Dropdown, DropdownOption } from '../../../components/Dropdown'; | |
import './styles.css'; | |
const linkify = linkifyIt(); | |
const MAX_DESCRIPTION = 90; | |
const MAX_BUTTON_TEXT = 25; | |
class LayoutComponent extends Component { | |
static propTypes = { | |
expanded: PropTypes.bool, | |
doExpand: PropTypes.func, | |
doCollapse: PropTypes.func, | |
onExpandEvent: PropTypes.func, | |
config: PropTypes.object, | |
onChange: PropTypes.func, | |
currentState: PropTypes.object, | |
translations: PropTypes.object, | |
showModalforEdit: PropTypes.bool, | |
}; | |
state: Object = { | |
showModal: false, | |
boxTitle: '', | |
boxText: '', | |
ctaTargetLink: '', | |
buttonText: '', | |
ctaTargetLinkOption: this.props.config.defaultTargetOption, | |
linkError: false, | |
}; | |
componentWillReceiveProps(props) { | |
if (this.props.expanded && !props.expanded) { | |
this.setState({ | |
showModal: false, | |
boxTitle: '', | |
boxText: '', | |
ctaTargetLink: '', | |
buttonText: '', | |
ctaTargetLinkOption: this.props.config.defaultTargetOption, | |
}); | |
} | |
} | |
charactersLengthForDiscription = () => this.state.boxText.length || 0; | |
charactersLengthForButtonText= () => this.state.buttonText.length || 0; | |
charactersLengthForBoxTitle= () => this.state.boxTitle.length || 0; | |
addCTABox: Function = (): void => { | |
const { onChange } = this.props; | |
const { | |
boxTitle, boxText, buttonText, ctaTargetLink, ctaTargetLinkOption, | |
} = this.state; | |
onChange('ctaBox', boxTitle, boxText, buttonText, ctaTargetLink, ctaTargetLinkOption); | |
}; | |
updateValue: Function = (event: Object): void => { | |
let { linkError } = this.state; | |
const links = linkify.match(event.target.value); | |
const linkifiedTarget = links && links[0] ? links[0].url : ''; | |
let { value } = event.target; | |
if (linkifiedTarget === '' && event.target.name === 'ctaTargetLink') { | |
linkError = true; | |
} else if (event.target.name === 'ctaTargetLink') { | |
linkError = false; | |
} | |
if (event.target.name === 'boxTitle' && event.target.value.length > 25) { | |
value = this.state.boxTitle; | |
} | |
if (event.target.name === 'boxText' && event.target.value.length > 90) { | |
value = this.state.boxText; | |
} | |
if (event.target.name === 'buttonText' && event.target.value.length > 25) { | |
value = this.state.buttonText; | |
} | |
this.setState({ | |
[`${event.target.name}`]: value, | |
linkError, | |
}); | |
}; | |
updateTargetOption: Function = (event: Object): void => { | |
this.setState({ | |
ctaTargetLinkOption: event.target.checked ? '_blank' : '_self', | |
}); | |
}; | |
hideModal: Function = (): void => { | |
this.setState({ | |
showModal: false, | |
}); | |
}; | |
signalExpandShowModal = () => { | |
const { onExpandEvent, currentState: { ctaBox } } = this.props; | |
const { ctaTargetLinkOption } = this.state; | |
onExpandEvent(); | |
this.setState({ | |
showModal: true, | |
boxTitle: (ctaBox && ctaBox.boxTitle) || '', | |
boxText: (ctaBox && ctaBox.boxText) || '', | |
ctaTargetLink: (ctaBox && ctaBox.ctaTargetLink) || '', | |
ctaTargetLinkOption: (ctaBox && ctaBox.targetOption) || ctaTargetLinkOption, | |
buttonText: (ctaBox && ctaBox.buttonText) || '', | |
}); | |
} | |
forceExpandAndShowModal: Function = (): void => { | |
const { doExpand, currentState: { ctaBox } } = this.props; | |
const { ctaTargetLinkOption } = this.state; | |
doExpand(); | |
this.setState({ | |
showModal: true, | |
boxTitle: (ctaBox && ctaBox.boxTitle) || '', | |
boxText: (ctaBox && ctaBox.boxText) || '', | |
ctaTargetLink: ctaBox && ctaBox.ctaTargetLink, | |
ctaTargetLinkOption: (ctaBox && ctaBox.targetOption) || ctaTargetLinkOption, | |
buttonText: (ctaBox && ctaBox.buttonText) || '', | |
}); | |
} | |
renderAddCTABoxModal() { | |
const { | |
config: { popupClassName }, doCollapse, translations, currentState: { ctaBox }, | |
} = this.props; | |
const { | |
buttonText, ctaTargetLink, linkError, boxTitle, boxText, | |
} = this.state; | |
const isLinkError = !(!linkError && ctaTargetLink && ctaTargetLink.length > 0); | |
return ( | |
<React.Fragment> | |
{this.state.showModal && <div className="ctabox-overlay" onClick={this.hideModal} />} | |
<div | |
className={classNames('rdw-modal', popupClassName)} | |
onClick={stopPropagation} | |
> | |
<h5 className="rdw-modal-header"> | |
{ctaBox ? translations['components.controls.ctaBox.headlineEdit'] : translations['components.controls.ctaBox.headlineAdd']} | |
</h5> | |
<label className="rdw-modal-label" htmlFor="boxTitle"> | |
{translations['components.controls.ctaBox.boxTitle']} | |
</label> | |
<div className="form-input-wrapper"> | |
<input | |
id="boxTitle" | |
className="form-control-box" | |
onChange={this.updateValue} | |
onBlur={this.updateValue} | |
name="boxTitle" | |
value={boxTitle} | |
/> | |
<i>{MAX_BUTTON_TEXT - this.charactersLengthForBoxTitle()}</i> | |
</div> | |
<label className="rdw-modal-label" htmlFor="boxText"> | |
{translations['components.controls.ctaBox.boxText']} | |
</label> | |
<div className="form-input-wrapper"> | |
<input | |
id="boxText" | |
className="form-control-box" | |
onChange={this.updateValue} | |
onBlur={this.updateValue} | |
name="boxText" | |
value={boxText} | |
/> | |
<i>{MAX_DESCRIPTION - this.charactersLengthForDiscription()}</i> | |
</div> | |
<label className="rdw-modal-label" htmlFor="buttonText"> | |
{translations['components.controls.ctaBox.buttonText']} | |
</label> | |
<div className="form-input-wrapper"> | |
<input | |
id="buttonText" | |
className="form-control-box" | |
onChange={this.updateValue} | |
onBlur={this.updateValue} | |
name="buttonText" | |
value={buttonText} | |
/> | |
<i>{MAX_BUTTON_TEXT - this.charactersLengthForButtonText()}</i> | |
</div> | |
<label className="rdw-modal-label" htmlFor="ctaTargetLink"> | |
{translations['components.controls.ctaBox.ctaTargetLink']} | |
</label> | |
<div className="form-input-wrapper-with-icon-link" style={linkError && ctaTargetLink && ctaTargetLink.length > 0 ? { borderColor: '#FC5457' } : { borderColor: '#DEDEDE' }}> | |
<i className="mi-link" /> | |
<input | |
id="ctaTargetLink" | |
className="form-control-box" | |
onChange={this.updateValue} | |
onBlur={this.updateValue} | |
name="ctaTargetLink" | |
value={ctaTargetLink} | |
placeholder="https://example.com" | |
/> | |
{ !isLinkError && <i className="mi-check-circle" style={{ color: '#4CAF50' }} />} | |
{ linkError && ctaTargetLink && ctaTargetLink.length > 0 && <i className="mi-highlight-off" onClick={() => this.setState({ ctaTargetLink: '' })} style={{ color: '#FC5457' }} />} | |
</div> | |
{linkError && ctaTargetLink && ctaTargetLink.length > 0 && <i className="rwd-link-error-message" >URL adress is not valid</i>} | |
<span className="rdw-modal-buttonsection"> | |
<button | |
className="rdw-modal-btn" | |
onClick={doCollapse} | |
> | |
{translations['generic.cancel']} | |
</button> | |
<button | |
className={!ctaTargetLink || !buttonText || isLinkError ? 'rdw-modal-btn-disabled' : 'rdw-modal-btn'} | |
onClick={this.addCTABox} | |
disabled={!ctaTargetLink || !buttonText || isLinkError} | |
> | |
{translations['components.controls.ctaBox.AddCta']} | |
</button> | |
</span> | |
</div> | |
</React.Fragment> | |
); | |
} | |
renderInFlatList(): Object { | |
const { | |
config: { options, link, className }, | |
expanded, | |
showModalforEdit, | |
translations, | |
} = this.props; | |
const { showModal } = this.state; | |
if (showModalforEdit && !showModal) { | |
this.forceExpandAndShowModal(); | |
} | |
return ( | |
<div className={classNames('rdw-ctabox-wrapper', className)} aria-label="rdw-ctabox-control"> | |
{options.indexOf('link') >= 0 && <Option | |
value="unordered-list-item" | |
className={classNames(link.className)} | |
onClick={this.signalExpandShowModal} | |
aria-haspopup="true" | |
aria-expanded={showModal} | |
title={link.title || translations['components.controls.ctaBox.link']} | |
> | |
<img | |
src={link.icon} | |
alt="" | |
/> | |
</Option>} | |
{expanded && showModal ? this.renderAddCTABoxModal() : undefined} | |
</div> | |
); | |
} | |
renderInDropDown(): Object { | |
const { | |
expanded, | |
onExpandEvent, | |
doCollapse, | |
doExpand, | |
onChange, | |
config, | |
translations, | |
} = this.props; | |
const { | |
options, link, className, dropdownClassName, title, | |
} = config; | |
const { showModal } = this.state; | |
return ( | |
<div | |
className="rdw-ctabox-wrapper" | |
aria-haspopup="true" | |
aria-label="rdw-ctabox-control" | |
aria-expanded={expanded} | |
title={title} | |
> | |
<Dropdown | |
className={classNames('rdw-ctabox-dropdown', className)} | |
optionWrapperClassName={classNames(dropdownClassName)} | |
onChange={onChange} | |
expanded={expanded && !showModal} | |
doExpand={doExpand} | |
doCollapse={doCollapse} | |
onExpandEvent={onExpandEvent} | |
> | |
<img | |
src={getFirstIcon(config)} | |
alt="" | |
/> | |
{options.indexOf('link') >= 0 && | |
<DropdownOption | |
onClick={this.forceExpandAndShowModal} | |
className={classNames('rdw-ctabox-dropdownoption', link.className)} | |
title={link.title || translations['components.controls.ctaBox.link']} | |
> | |
<img | |
src={link.icon} | |
alt="" | |
/> | |
</DropdownOption>} | |
</Dropdown> | |
{expanded && showModal ? this.renderAddCTABoxModal() : undefined} | |
</div> | |
); | |
} | |
render(): Object { | |
const { config: { inDropdown } } = this.props; | |
if (inDropdown) { | |
return this.renderInDropDown(); | |
} | |
return this.renderInFlatList(); | |
} | |
} | |
export default LayoutComponent; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// CTABox/index.js | |
import React, { Component } from 'react'; | |
import PropTypes from 'prop-types'; | |
import { EditorState, Modifier } from 'draft-js'; | |
import { | |
getSelectionText, | |
getEntityRange, | |
getSelectionEntity, | |
} from 'draftjs-utils'; | |
import linkifyIt from 'linkify-it'; | |
import LayoutComponent from './Component'; | |
const linkify = linkifyIt(); | |
class CTABox extends Component { | |
static propTypes = { | |
editorState: PropTypes.object.isRequired, | |
onChange: PropTypes.func.isRequired, | |
modalHandler: PropTypes.object, | |
config: PropTypes.object, | |
translations: PropTypes.object, | |
modalCtaBoxOpen: PropTypes.bool, | |
}; | |
static getDerivedStateFromProps(nextProps, state) { | |
if (nextProps.modalCtaBoxOpen.entityKey !== state.currentEntity) { | |
return { currentEntity: nextProps.modalCtaBoxOpen.entityKey }; | |
} | |
return state; | |
} | |
state = { | |
expanded: false, | |
link: undefined, | |
selectionText: undefined, | |
}; | |
componentDidMount() { | |
const { editorState, modalHandler, modalCtaBoxOpen: { entityKey } } = this.props; | |
if (editorState) { | |
this.setState({ | |
currentEntity: getSelectionEntity(editorState) || entityKey, | |
}); | |
} | |
modalHandler.registerCallBack(this.expandCollapse); | |
} | |
componentWillUnmount() { | |
const { modalHandler } = this.props; | |
modalHandler.deregisterCallBack(this.expandCollapse); | |
} | |
onExpandEvent: Function = () => { | |
this.signalExpanded = !this.state.expanded; | |
}; | |
onChange = (action, boxTitle, boxText, buttonText, target, targetOption) => { | |
if (action === 'ctaBox') { | |
const links = linkify.match(target); | |
const linkifiedTarget = links && links[0] ? links[0].url : ''; | |
this.addCTABox(boxTitle, boxText, buttonText, linkifiedTarget, targetOption); | |
} | |
} | |
getCurrentValues = () => { | |
const { editorState, modalCtaBoxOpen: { isOpen } } = this.props; | |
const { currentEntity } = this.state; | |
const contentState = editorState.getCurrentContent(); | |
const currentValues = {}; | |
if (currentEntity && (contentState.getEntity(currentEntity).get('type') === 'CTA_BOX')) { | |
currentValues.ctaBox = {}; | |
const entityRange = currentEntity && getEntityRange(editorState, currentEntity); | |
currentValues.ctaBox.ctaTargetLink = currentEntity && contentState.getEntity(currentEntity).get('data').url; | |
currentValues.ctaBox.ctaTargetLinkOption = currentEntity && contentState.getEntity(currentEntity).get('data').targetOption; | |
currentValues.ctaBox.buttonText = (entityRange && entityRange.text) || contentState.getEntity(currentEntity).get('data').ctaButtonText; | |
currentValues.ctaBox.boxTitle = currentEntity && contentState.getEntity(currentEntity).get('data').ctaTitle; | |
currentValues.ctaBox.boxText = currentEntity && contentState.getEntity(currentEntity).get('data').ctaText; | |
currentValues.showModalforEdit = isOpen; | |
} | |
currentValues.selectionText = getSelectionText(editorState); | |
return currentValues; | |
} | |
doExpand: Function = () => { | |
this.setState({ | |
expanded: true, | |
}); | |
}; | |
expandCollapse: Function = () => { | |
this.setState({ | |
expanded: this.signalExpanded, | |
}); | |
this.signalExpanded = false; | |
} | |
doCollapse: Function = () => { | |
const { clearStateEntities } = this.props; | |
clearStateEntities(); | |
this.setState({ | |
expanded: false, | |
}); | |
}; | |
addCTABox = (boxTitle, boxText, buttonText, url, ctaTargetLinkOption) => { | |
const { editorState, onChange } = this.props; | |
const { currentEntity } = this.state; | |
let selection = editorState.getSelection(); | |
if (currentEntity) { | |
const entityRange = getEntityRange(editorState, currentEntity); | |
selection = selection.merge({ | |
anchorOffset: entityRange.start, | |
focusOffset: entityRange.end, | |
}); | |
} | |
const entityKey = editorState | |
.getCurrentContent() | |
.createEntity('CTA_BOX', 'MUTABLE', { | |
ctaTitle: boxTitle, | |
ctaText: boxText, | |
ctaButtonText: buttonText, | |
url, | |
targetOption: ctaTargetLinkOption, | |
}) | |
.getLastCreatedEntityKey(); | |
let contentState = Modifier.replaceText( | |
editorState.getCurrentContent(), | |
selection, | |
`${buttonText}`, | |
editorState.getCurrentInlineStyle(), | |
entityKey, | |
); | |
let newEditorState = EditorState.push(editorState, contentState, 'insert-characters'); | |
// insert a blank space after ctaBox | |
if (!currentEntity) { | |
selection = newEditorState.getSelection().merge({ | |
anchorOffset: selection.get('anchorOffset') + buttonText.length, | |
focusOffset: selection.get('focusOffset') + buttonText.length, | |
}); | |
newEditorState = EditorState.acceptSelection(newEditorState, selection); | |
contentState = Modifier.insertText( | |
newEditorState.getCurrentContent(), | |
selection, | |
' ', | |
newEditorState.getCurrentInlineStyle(), | |
undefined, | |
); | |
} | |
onChange(EditorState.push(newEditorState, contentState, 'insert-characters')); | |
this.doCollapse(); | |
}; | |
render() { | |
const { config, translations, clearStateEntities } = this.props; | |
const { expanded } = this.state; | |
const { ctaBox, selectionText, showModalforEdit } = this.getCurrentValues(); | |
const CTABoxComponent = config.component || LayoutComponent; | |
return ( | |
<CTABoxComponent | |
config={config} | |
translations={translations} | |
expanded={expanded} | |
showModalforEdit={showModalforEdit} | |
onExpandEvent={this.onExpandEvent} | |
clearStateEntities={clearStateEntities} | |
doExpand={this.doExpand} | |
doCollapse={this.doCollapse} | |
currentState={{ | |
ctaBox, | |
selectionText, | |
}} | |
onChange={this.onChange} | |
/> | |
); | |
} | |
} | |
export default CTABox; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Here is the actual Editor component which will be imported in UI . Also want to note , this is a fork of react-draft-wysivgg | |
Editor/index.js | |
import React, { Component } from 'react'; | |
import PropTypes from 'prop-types'; | |
import { | |
Editor, | |
EditorState, | |
RichUtils, | |
convertToRaw, | |
convertFromRaw, | |
CompositeDecorator, | |
Modifier, | |
SelectionState, | |
CharacterMetadata, | |
} from 'draft-js'; | |
import { | |
changeDepth, | |
handleNewLine, | |
blockRenderMap, | |
getCustomStyleMap, | |
extractInlineStyle, | |
getSelectedBlocksType, | |
getSelectionText, | |
getEntityRange, | |
getSelectionEntity, | |
} from 'draftjs-utils'; | |
import classNames from 'classnames'; | |
import ModalHandler from '../event-handler/modals'; | |
import FocusHandler from '../event-handler/focus'; | |
import KeyDownHandler from '../event-handler/keyDown'; | |
import SuggestionHandler from '../event-handler/suggestions'; | |
import blockStyleFn from '../utils/BlockStyle'; | |
import { mergeRecursive } from '../utils/toolbar'; | |
import { hasProperty, filter } from '../utils/common'; | |
import { handlePastedText } from '../utils/handlePaste'; | |
import Controls from '../controls'; | |
import getLinkDecorator from '../decorators/Link'; | |
import getCTABoxDecorator from '../decorators/CTABox'; | |
import getMentionDecorators from '../decorators/Mention'; | |
import getHashtagDecorator from '../decorators/HashTag'; | |
import getBlockRenderFunc from '../renderer'; | |
import defaultToolbar from '../config/defaultToolbar'; | |
import localeTranslations from '../i18n'; | |
import './styles.css'; | |
import '../../css/Draft.css'; | |
export default class WysiwygEditor extends Component { | |
static propTypes = { | |
onChange: PropTypes.func, | |
onEditorStateChange: PropTypes.func, | |
onContentStateChange: PropTypes.func, | |
// initialContentState is deprecated | |
initialContentState: PropTypes.object, | |
defaultContentState: PropTypes.object, | |
contentState: PropTypes.object, | |
editorState: PropTypes.object, | |
defaultEditorState: PropTypes.object, | |
toolbarOnFocus: PropTypes.bool, | |
spellCheck: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types | |
stripPastedStyles: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types | |
toolbar: PropTypes.object, | |
toolbarCustomButtons: PropTypes.array, | |
toolbarClassName: PropTypes.string, | |
toolbarHidden: PropTypes.bool, | |
locale: PropTypes.string, | |
localization: PropTypes.object, | |
editorClassName: PropTypes.string, | |
wrapperClassName: PropTypes.string, | |
toolbarStyle: PropTypes.object, | |
editorStyle: PropTypes.object, | |
wrapperStyle: PropTypes.object, | |
uploadCallback: PropTypes.func, | |
onFocus: PropTypes.func, | |
onBlur: PropTypes.func, | |
onTab: PropTypes.func, | |
mention: PropTypes.object, | |
hashtag: PropTypes.object, | |
textAlignment: PropTypes.string, // eslint-disable-line react/no-unused-prop-types | |
readOnly: PropTypes.bool, | |
tabIndex: PropTypes.number, // eslint-disable-line react/no-unused-prop-types | |
placeholder: PropTypes.string, // eslint-disable-line react/no-unused-prop-types | |
ariaLabel: PropTypes.string, | |
ariaOwneeID: PropTypes.string, // eslint-disable-line react/no-unused-prop-types | |
ariaActiveDescendantID: PropTypes.string, // eslint-disable-line react/no-unused-prop-types | |
ariaAutoComplete: PropTypes.string, // eslint-disable-line react/no-unused-prop-types | |
ariaDescribedBy: PropTypes.string, // eslint-disable-line react/no-unused-prop-types | |
ariaExpanded: PropTypes.string, // eslint-disable-line react/no-unused-prop-types | |
ariaHasPopup: PropTypes.string, // eslint-disable-line react/no-unused-prop-types | |
customBlockRenderFunc: PropTypes.func, | |
wrapperId: PropTypes.number, | |
customDecorators: PropTypes.array, | |
editorRef: PropTypes.func, | |
}; | |
static defaultProps = { | |
toolbarOnFocus: false, | |
toolbarHidden: false, | |
stripPastedStyles: false, | |
localization: { locale: 'en', translations: {} }, | |
customDecorators: [], | |
}; | |
constructor(props) { | |
super(props); | |
const toolbar = mergeRecursive(defaultToolbar, props.toolbar); | |
this.state = { | |
editorState: props.editorState, | |
editorFocused: false, | |
toolbar, | |
modalCtaBoxOpen: { | |
isOpen: false, | |
entityKey: null, | |
}, | |
modalCtaBoxDeleted: false, | |
}; | |
const wrapperId = props.wrapperId | |
? props.wrapperId | |
: Math.floor(Math.random() * 10000); | |
this.wrapperId = `rdw-wrapper-${wrapperId}`; | |
this.modalHandler = new ModalHandler(); | |
this.focusHandler = new FocusHandler(); | |
this.blockRendererFn = getBlockRenderFunc( | |
{ | |
isReadOnly: this.isReadOnly, | |
isImageAlignmentEnabled: this.isImageAlignmentEnabled, | |
isVideoAlignmentEnabled: this.isVideoAlignmentEnabled, | |
isCtaImageAlignmentEnabled: this.isCtaImageAlignmentEnabled, | |
getEditorState: this.getEditorState, | |
onChange: this.onChange, | |
deleteEntity: this.onDeleteClicked, | |
}, | |
props.customBlockRenderFunc, | |
); | |
this.editorProps = this.filterEditorProps(props); | |
this.customStyleMap = getCustomStyleMap(); | |
} | |
componentWillMount() { | |
this.compositeDecorator = this.getCompositeDecorator(); | |
console.log(this.props, 'will mounttt'); | |
const editorState = this.createEditorState(this.compositeDecorator); | |
extractInlineStyle(editorState); | |
this.setState({ | |
editorState, | |
}); | |
} | |
componentDidMount() { | |
this.modalHandler.init(this.wrapperId); | |
} | |
componentWillReceiveProps(props) { | |
const newState = {}; | |
if (this.props.toolbar !== props.toolbar) { | |
const toolbar = mergeRecursive(defaultToolbar, props.toolbar); | |
newState.toolbar = toolbar; | |
} | |
console.log(this.props.editorState, 'this.props.editorState'); | |
console.log(props.editorState, 'props.editorState'); | |
if ( | |
hasProperty(props, 'editorState') && | |
this.props.editorState !== props.editorState | |
) { | |
if (props.editorState) { | |
newState.editorState = EditorState.set(props.editorState, { | |
decorator: this.compositeDecorator, | |
}); | |
} else { | |
newState.editorState = EditorState.createEmpty(this.compositeDecorator); | |
} | |
} else if ( | |
hasProperty(props, 'contentState') && | |
this.props.contentState !== props.contentState | |
) { | |
if (props.contentState) { | |
const newEditorState = this.changeEditorState(props.contentState); | |
if (newEditorState) { | |
newState.editorState = newEditorState; | |
} | |
} else { | |
newState.editorState = EditorState.createEmpty(this.compositeDecorator); | |
} | |
} | |
if ( | |
props.editorState !== this.props.editorState || | |
props.contentState !== this.props.contentState | |
) { | |
extractInlineStyle(newState.editorState); | |
} | |
this.setState(newState); | |
this.editorProps = this.filterEditorProps(props); | |
this.customStyleMap = getCustomStyleMap(); | |
} | |
onEditorBlur = () => { | |
this.setState({ | |
editorFocused: false, | |
}); | |
}; | |
onEditCTABoxClicked = (entityKey) => { | |
const modalCtaBoxOpen = { entityKey, isOpen: !this.state.modalCtaBoxOpen.isOpen }; | |
this.setState({ | |
modalCtaBoxOpen, | |
}); | |
}; | |
onDeleteCTABoxClicked = (entityKey) => { | |
const { editorState } = this.state; | |
let selection = editorState.getSelection(); | |
if (entityKey) { | |
const entityRange = getEntityRange(editorState, entityKey); | |
if (entityRange) { | |
selection = selection.merge({ | |
anchorOffset: entityRange.start, | |
focusOffset: entityRange.end, | |
}); | |
this.setState({ | |
editorState: RichUtils.toggleLink(editorState, selection, null), | |
}); | |
} else { | |
let chars; | |
let content = editorState.getCurrentContent(); | |
const blocks = content.getBlockMap().map((block) => { | |
if (block.getType() === 'atomic') { | |
chars = block.getCharacterList().map((char) => { | |
const entity = char.getEntity(); | |
if (entityKey === entity) { | |
return CharacterMetadata.applyEntity(char, null); | |
} | |
return char; | |
}); | |
} | |
return block.set('characterList', chars); | |
}); | |
content = content.replaceEntityData(entityKey, {}); | |
content.merge({ blockMap: content.getBlockMap().merge(blocks) }); | |
this.setState({ editorState: EditorState.createWithContent(content) }); | |
} | |
} | |
}; | |
onDeleteClicked = (blockKey) => { | |
const { editorState } = this.state; | |
const contentState = editorState.getCurrentContent(); | |
const afterKey = contentState.getKeyAfter(blockKey); | |
const targetRange = new SelectionState({ | |
anchorKey: blockKey, | |
anchorOffset: 0, | |
focusKey: afterKey, | |
focusOffset: 0, | |
}); | |
let newContentState = Modifier.setBlockType( | |
contentState, | |
targetRange, | |
'unstyled', | |
); | |
newContentState = Modifier.removeRange(newContentState, targetRange, 'backward'); | |
const newEditorState = EditorState.push(editorState, newContentState, 'remove-range'); | |
this.setState({ | |
editorState: newEditorState, | |
}); | |
} | |
onEditorFocus = (event) => { | |
const { onFocus } = this.props; | |
this.setState({ | |
editorFocused: true, | |
}); | |
const editFocused = this.focusHandler.isEditorFocused(); | |
if (onFocus && editFocused) { | |
onFocus(event); | |
} | |
}; | |
onEditorMouseDown = () => { | |
this.focusHandler.onEditorMouseDown(); | |
}; | |
onTab = (event) => { | |
const { onTab } = this.props; | |
if (!onTab || !onTab(event)) { | |
const editorState = changeDepth( | |
this.state.editorState, | |
event.shiftKey ? -1 : 1, | |
4, | |
); | |
if (editorState && editorState !== this.state.editorState) { | |
this.onChange(editorState); | |
event.preventDefault(); | |
} | |
} | |
}; | |
onUpDownArrow = (event) => { | |
if (SuggestionHandler.isOpen()) { | |
event.preventDefault(); | |
} | |
}; | |
onToolbarFocus = (event) => { | |
const { onFocus } = this.props; | |
if (onFocus && this.focusHandler.isToolbarFocused()) { | |
onFocus(event); | |
} | |
}; | |
onWrapperBlur = (event) => { | |
const { onBlur } = this.props; | |
if (onBlur && this.focusHandler.isEditorBlur(event)) { | |
onBlur(event, this.getEditorState()); | |
} | |
}; | |
onChange = (editorState) => { | |
const { readOnly, onEditorStateChange } = this.props; | |
if ( | |
!readOnly && | |
!( | |
getSelectedBlocksType(editorState) === 'atomic' && | |
editorState.getSelection().isCollapsed | |
) | |
) { | |
if (onEditorStateChange) { | |
onEditorStateChange(editorState, this.props.wrapperId); | |
} | |
if (!hasProperty(this.props, 'editorState')) { | |
this.setState({ editorState }, this.afterChange(editorState)); | |
} else { | |
this.afterChange(editorState); | |
} | |
} | |
}; | |
setWrapperReference = (ref) => { | |
this.wrapper = ref; | |
}; | |
setEditorReference = (ref) => { | |
if (this.props.editorRef) { | |
this.props.editorRef(ref); | |
} | |
this.editor = ref; | |
}; | |
getCompositeDecorator = () => { | |
const decorators = [ | |
...this.props.customDecorators, | |
getLinkDecorator({ | |
showOpenOptionOnHover: this.state.toolbar.link.showOpenOptionOnHover, | |
}), | |
getCTABoxDecorator({ | |
showOpenOptionOnHover: this.state.toolbar.ctaBox.showOpenOptionOnHover, | |
onEditCTABoxClicked: this.onEditCTABoxClicked, | |
onDeleteCTABoxClicked: this.onDeleteCTABoxClicked, | |
}), | |
]; | |
if (this.props.mention) { | |
decorators.push(...getMentionDecorators({ | |
...this.props.mention, | |
onChange: this.onChange, | |
getEditorState: this.getEditorState, | |
getSuggestions: this.getSuggestions, | |
getWrapperRef: this.getWrapperRef, | |
modalHandler: this.modalHandler, | |
})); | |
} | |
if (this.props.hashtag) { | |
decorators.push(getHashtagDecorator(this.props.hashtag)); | |
} | |
return new CompositeDecorator(decorators); | |
}; | |
getWrapperRef = () => this.wrapper; | |
getEditorState = () => this.state.editorState; | |
getSuggestions = () => this.props.mention && this.props.mention.suggestions; | |
clearStateEntities =() => { | |
this.setState({ | |
modalCtaBoxOpen: { | |
isOpen: false, | |
}, | |
}); | |
} | |
afterChange = (editorState) => { | |
setTimeout(() => { | |
const { onChange, onContentStateChange } = this.props; | |
if (onChange) { | |
onChange(convertToRaw(editorState.getCurrentContent())); | |
} | |
if (onContentStateChange) { | |
onContentStateChange(convertToRaw(editorState.getCurrentContent())); | |
} | |
}); | |
}; | |
isReadOnly = () => this.props.readOnly; | |
isImageAlignmentEnabled = () => this.state.toolbar.image.alignmentEnabled; | |
isVideoAlignmentEnabled = () => this.state.toolbar.video.alignmentEnabled; | |
isCtaImageAlignmentEnabled = () => this.state.toolbar.ctaImage.alignmentEnabled; | |
createEditorState = (compositeDecorator) => { | |
let editorState; | |
if (hasProperty(this.props, 'editorState')) { | |
if (this.props.editorState) { | |
editorState = EditorState.set(this.props.editorState, { | |
decorator: compositeDecorator, | |
}); | |
} | |
} else if (hasProperty(this.props, 'defaultEditorState')) { | |
if (this.props.defaultEditorState) { | |
editorState = EditorState.set(this.props.defaultEditorState, { | |
decorator: compositeDecorator, | |
}); | |
} | |
} else if (hasProperty(this.props, 'contentState')) { | |
if (this.props.contentState) { | |
const contentState = convertFromRaw(this.props.contentState); | |
editorState = EditorState.createWithContent( | |
contentState, | |
compositeDecorator, | |
); | |
editorState = EditorState.moveSelectionToEnd(editorState); | |
} | |
} else if ( | |
hasProperty(this.props, 'defaultContentState') || | |
hasProperty(this.props, 'initialContentState') | |
) { | |
let contentState = | |
this.props.defaultContentState || this.props.initialContentState; | |
if (contentState) { | |
contentState = convertFromRaw(contentState); | |
editorState = EditorState.createWithContent( | |
contentState, | |
compositeDecorator, | |
); | |
editorState = EditorState.moveSelectionToEnd(editorState); | |
} | |
} | |
if (!editorState) { | |
editorState = EditorState.createEmpty(compositeDecorator); | |
} | |
return editorState; | |
}; | |
filterEditorProps = props => | |
filter(props, [ | |
'onChange', | |
'onEditorStateChange', | |
'onContentStateChange', | |
'initialContentState', | |
'defaultContentState', | |
'contentState', | |
'editorState', | |
'defaultEditorState', | |
'locale', | |
'localization', | |
'toolbarOnFocus', | |
'toolbar', | |
'toolbarCustomButtons', | |
'toolbarClassName', | |
'editorClassName', | |
'toolbarHidden', | |
'wrapperClassName', | |
'toolbarStyle', | |
'editorStyle', | |
'wrapperStyle', | |
'uploadCallback', | |
'onFocus', | |
'onBlur', | |
'onTab', | |
'mention', | |
'hashtag', | |
'ariaLabel', | |
'customBlockRenderFunc', | |
'customDecorators', | |
'handlePastedText', | |
]); | |
changeEditorState = (contentState) => { | |
const newContentState = convertFromRaw(contentState); | |
let { editorState } = this.state; | |
editorState = EditorState.push( | |
editorState, | |
newContentState, | |
'insert-characters', | |
); | |
editorState = EditorState.moveSelectionToEnd(editorState); | |
return editorState; | |
}; | |
focusEditor = () => { | |
setTimeout(() => { | |
this.editor.focus(); | |
}); | |
}; | |
handleKeyCommand = (command) => { | |
const { editorState, toolbar: { inline } } = this.state; | |
if (inline && inline.options.indexOf(command) >= 0) { | |
const newState = RichUtils.handleKeyCommand(editorState, command); | |
if (newState) { | |
this.onChange(newState); | |
return true; | |
} | |
} | |
return false; | |
}; | |
handleReturn = (event) => { | |
if (SuggestionHandler.isOpen()) { | |
return true; | |
} | |
const editorState = handleNewLine(this.state.editorState, event); | |
if (editorState) { | |
this.onChange(editorState); | |
return true; | |
} | |
return false; | |
}; | |
handlePastedText = (text, html) => { | |
const { editorState } = this.state; | |
if (this.props.handlePastedText) { | |
return this.props.handlePastedText( | |
text, | |
html, | |
editorState, | |
this.onChange, | |
); | |
} | |
if (!this.props.stripPastedStyles) { | |
return handlePastedText(text, html, editorState, this.onChange); | |
} | |
return false; | |
}; | |
preventDefault = (event) => { | |
if (event.target.tagName === 'INPUT' || event.target.tagName === 'LABEL') { | |
this.focusHandler.onInputMouseDown(); | |
} else { | |
event.preventDefault(); | |
} | |
}; | |
render() { | |
const { | |
editorState, editorFocused, toolbar, modalCtaBoxOpen, | |
} = this.state; | |
const { | |
locale, | |
localization: { locale: newLocale, translations }, | |
toolbarCustomButtons, | |
toolbarOnFocus, | |
toolbarClassName, | |
toolbarHidden, | |
editorClassName, | |
wrapperClassName, | |
toolbarStyle, | |
editorStyle, | |
wrapperStyle, | |
uploadCallback, | |
ariaLabel, | |
} = this.props; | |
const controlProps = { | |
modalHandler: this.modalHandler, | |
editorState, | |
onChange: this.onChange, | |
translations: { | |
...localeTranslations[locale || newLocale], | |
...translations, | |
}, | |
}; | |
const toolbarShow = | |
editorFocused || this.focusHandler.isInputFocused() || !toolbarOnFocus; | |
return ( | |
<div | |
id={this.wrapperId} | |
className={classNames(wrapperClassName, 'rdw-editor-wrapper')} | |
style={wrapperStyle} | |
onClick={this.modalHandler.onEditorClick} | |
onBlur={this.onWrapperBlur} | |
aria-label="rdw-wrapper" | |
> | |
{!toolbarHidden && ( | |
<div | |
className={classNames('rdw-editor-toolbar', toolbarClassName)} | |
style={{ | |
visibility: toolbarShow ? 'visible' : 'hidden', | |
...toolbarStyle, | |
}} | |
onMouseDown={this.preventDefault} | |
aria-label="rdw-toolbar" | |
aria-hidden={(!editorFocused && toolbarOnFocus).toString()} | |
onFocus={this.onToolbarFocus} | |
> | |
{toolbar.options.map((opt, index) => { | |
const Control = Controls[opt]; | |
const config = toolbar[opt]; | |
if (['image', 'video', 'ctaImage'].includes(opt) && uploadCallback) { | |
config.uploadCallback = uploadCallback; | |
} | |
return (<Control | |
key={index} | |
clearStateEntities={this.clearStateEntities} | |
modalCtaBoxOpen={modalCtaBoxOpen} | |
{...controlProps} | |
config={config} | |
/>); | |
})} | |
{toolbarCustomButtons && | |
toolbarCustomButtons.map((button, index) => | |
React.cloneElement(button, { key: index, ...controlProps }))} | |
</div> | |
)} | |
<div | |
ref={this.setWrapperReference} | |
className={classNames(editorClassName, 'rdw-editor-main')} | |
style={editorStyle} | |
onClick={this.focusEditor} | |
onFocus={this.onEditorFocus} | |
onBlur={this.onEditorBlur} | |
onKeyDown={KeyDownHandler.onKeyDown} | |
onMouseDown={this.onEditorMouseDown} | |
> | |
<Editor | |
ref={this.setEditorReference} | |
onTab={this.onTab} | |
onUpArrow={this.onUpDownArrow} | |
onDownArrow={this.onUpDownArrow} | |
editorState={editorState} | |
onChange={this.onChange} | |
blockStyleFn={blockStyleFn} | |
customStyleMap={getCustomStyleMap()} | |
handleReturn={this.handleReturn} | |
handlePastedText={this.handlePastedText} | |
blockRendererFn={this.blockRendererFn} | |
handleKeyCommand={this.handleKeyCommand} | |
ariaLabel={ariaLabel || 'rdw-editor'} | |
blockRenderMap={blockRenderMap} | |
{...this.editorProps} | |
/> | |
</div> | |
</div> | |
); | |
} | |
} | |
// todo: evaluate draftjs-utils to move some methods here | |
// todo: move color near font-family |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Here it gets the html content from UI , and creates Editor components | |
// fork of html-to-draftjs | |
import {CharacterMetadata, ContentBlock, genKey, Entity} from 'draft-js'; | |
import {Map, List, OrderedMap, OrderedSet} from 'immutable'; | |
import getSafeBodyFromHTML from './getSafeBodyFromHTML'; | |
import { | |
createTextChunk, | |
getSoftNewlineChunk, | |
getEmptyChunk, | |
getBlockDividerChunk, | |
getFirstBlockChunk, | |
getAtomicBlockChunk, | |
joinChunks, | |
} from './chunkBuilder'; | |
import getBlockTypeForTag from './getBlockTypeForTag'; | |
import processInlineTag from './processInlineTag'; | |
import getBlockData from './getBlockData'; | |
import getEntityId from './getEntityId'; | |
const SPACE = ' '; | |
const REGEX_NBSP = new RegExp(' ', 'g'); | |
let firstBlock = true; | |
type CustomChunkGenerator = (nodeName: string, node: HTMLElement) => ?{type: string, mutability: string, data: {}}; | |
function genFragment( | |
node: Object, | |
inlineStyle: OrderedSet, | |
depth: number, | |
lastList: string, | |
inEntity: number, | |
customChunkGenerator: ?CustomChunkGenerator, | |
): Object { | |
const nodeName = node.nodeName.toLowerCase(); | |
if (customChunkGenerator) { | |
const value = customChunkGenerator(nodeName, node); | |
if (value) { | |
const entityId = Entity.__create( | |
value.type, | |
value.mutability, | |
value.data || {}, | |
); | |
return {chunk: getAtomicBlockChunk(entityId)}; | |
} | |
} | |
if (nodeName === 'div' && | |
node instanceof HTMLDivElement | |
) { | |
const entityConfig = {}; | |
entityConfig.ctaTitle = node.getElementsByTagName('H3')[0].innerHTML; | |
entityConfig.ctaText = node.getElementsByTagName('P')[0].innerHTML; | |
entityConfig.ctaButtonText = node.getElementsByTagName('A')[0].innerHTML; | |
entityConfig.url = node.getElementsByTagName('A')[0].getAttribute('href'); | |
entityConfig.targetOption = node.getElementsByTagName('A')[0].getAttribute('target'); | |
const entityId = Entity.__create( | |
'CTA_BOX', | |
'MUTABLE', | |
entityConfig, | |
); | |
return {chunk: getAtomicBlockChunk(entityId)}; | |
} | |
if (nodeName === 'a' && | |
node instanceof HTMLAnchorElement && | |
node.id === 'ctaimage-root' | |
) { | |
const image = node.getElementsByTagName('img')[0]; | |
const entityConfig = {}; | |
entityConfig.src = image.getAttribute ? image.getAttribute('src') || image.src : image.src; | |
entityConfig.alt = image.alt; | |
entityConfig.height = image.style.height; | |
entityConfig.width = image.style.width; | |
if (image.style.float) { | |
entityConfig.alignment = image.style.float; | |
} | |
entityConfig.linkUrl = node.href; | |
const entityId = Entity.__create( | |
'CTA_IMAGE', | |
'MUTABLE', | |
entityConfig, | |
); | |
return {chunk: getAtomicBlockChunk(entityId)}; | |
} | |
if (nodeName === '#text' && node.textContent !== '\n') { | |
return createTextChunk(node, inlineStyle, inEntity); | |
} | |
if (nodeName === 'br') { | |
return {chunk: getSoftNewlineChunk()}; | |
} | |
if ( | |
nodeName === 'img' && | |
node instanceof HTMLImageElement | |
) { | |
const entityConfig = {}; | |
entityConfig.src = node.getAttribute ? node.getAttribute('src') || node.src : node.src; | |
entityConfig.alt = node.alt; | |
entityConfig.height = node.style.height; | |
entityConfig.width = node.style.width; | |
if (node.style.float) { | |
entityConfig.alignment = node.style.float; | |
} | |
const entityId = Entity.__create( | |
'IMAGE', | |
'MUTABLE', | |
entityConfig, | |
); | |
return {chunk: getAtomicBlockChunk(entityId)}; | |
} | |
if ( | |
nodeName === 'video' && | |
node instanceof HTMLVideoElement | |
) { | |
const entityConfig = {}; | |
entityConfig.controls = true; | |
entityConfig.src = node.getAttribute ? node.getAttribute('src') || node.src : node.src; | |
entityConfig.alt = node.alt; | |
entityConfig.height = node.style.height; | |
entityConfig.width = node.style.width; | |
if (node.style.float) { | |
entityConfig.alignment = node.style.float; | |
} | |
const entityId = Entity.__create( | |
'VIDEO', | |
'MUTABLE', | |
entityConfig, | |
); | |
return {chunk: getAtomicBlockChunk(entityId)}; | |
} | |
if ( | |
nodeName === 'iframe' && | |
node instanceof HTMLIFrameElement | |
) { | |
const entityConfig = {}; | |
entityConfig.src = node.getAttribute ? node.getAttribute('src') || node.src : node.src; | |
entityConfig.height = node.height; | |
entityConfig.width = node.width; | |
const entityId = Entity.__create( | |
'EMBEDDED_LINK', | |
'MUTABLE', | |
entityConfig, | |
); | |
return {chunk: getAtomicBlockChunk(entityId)}; | |
} | |
const blockType = getBlockTypeForTag(nodeName, lastList, node.className); | |
let chunk; | |
if (blockType) { | |
if (nodeName === 'ul' || nodeName === 'ol') { | |
lastList = nodeName; | |
depth += 1; | |
} else { | |
if ( | |
blockType !== 'unordered-list-item' && | |
blockType !== 'ordered-list-item' | |
) { | |
lastList = ''; | |
depth = -1; | |
} | |
if (!firstBlock) { | |
chunk = getBlockDividerChunk( | |
blockType, | |
depth, | |
getBlockData(node) | |
); | |
} else { | |
chunk = getFirstBlockChunk( | |
blockType, | |
getBlockData(node) | |
); | |
firstBlock = false; | |
} | |
} | |
} | |
if (!chunk) { | |
chunk = getEmptyChunk(); | |
} | |
inlineStyle = processInlineTag(nodeName, node, inlineStyle); | |
let child = node.firstChild; | |
while (child) { | |
const entityId = getEntityId(child); | |
const {chunk: generatedChunk} = genFragment(child, inlineStyle, depth, lastList, (entityId || inEntity), customChunkGenerator); | |
chunk = joinChunks(chunk, generatedChunk); | |
const sibling = child.nextSibling; | |
child = sibling; | |
} | |
return {chunk}; | |
} | |
function getChunkForHTML(html: string, customChunkGenerator: ?CustomChunkGenerator): Object { | |
const sanitizedHtml = html.trim().replace(REGEX_NBSP, SPACE); | |
const safeBody = getSafeBodyFromHTML(sanitizedHtml); | |
if (!safeBody) { | |
return null; | |
} | |
firstBlock = true; | |
const {chunk} = genFragment(safeBody, new OrderedSet(), -1, '', undefined, customChunkGenerator); | |
return {chunk}; | |
} | |
export default function htmlToDraft(html: string, customChunkGenerator: ?CustomChunkGenerator): Object { | |
const chunkData = getChunkForHTML(html, customChunkGenerator); | |
if (chunkData) { | |
const {chunk} = chunkData; | |
let entityMap = new OrderedMap({}); | |
chunk.entities && chunk.entities.forEach(entity => { | |
if (entity) { | |
entityMap = entityMap.set(entity, Entity.__get(entity)); | |
} | |
}); | |
let start = 0; | |
return { | |
contentBlocks: chunk.text.split('\r') | |
.map( | |
(textBlock, ii) => { | |
const end = start + textBlock.length; | |
const inlines = chunk && chunk.inlines.slice(start, end); | |
const entities = chunk && chunk.entities.slice(start, end); | |
const characterList = new List( | |
inlines.map((style, index) => { | |
const data = {style, entity: null}; | |
if (entities[index]) { | |
data.entity = entities[index]; | |
} | |
return CharacterMetadata.create(data); | |
}), | |
); | |
start = end; | |
return new ContentBlock({ | |
key: genKey(), | |
type: (chunk && chunk.blocks[ii] && chunk.blocks[ii].type) || 'unstyled', | |
depth: chunk && chunk.blocks[ii] && chunk.blocks[ii].depth, | |
data: (chunk && chunk.blocks[ii] && chunk.blocks[ii].data) || new Map({}), | |
text: textBlock, | |
characterList, | |
}); | |
}, | |
), | |
entityMap, | |
}; | |
} | |
return null; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ctaBox from './CTABox'; | |
module.exports = { | |
ctaBox, | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// UI , which fetches the editor content from database and rendes the Editor | |
import PropTypes from 'prop-types'; | |
import React from 'react'; | |
import {EditorState, convertToRaw, ContentState} from 'draft-js'; | |
import {Editor} from 'react-draft-wysiwyg'; | |
import draftToHtml from 'draftjs-to-html'; | |
import htmlToDraft from 'html-to-draftjs'; | |
import PureBase from 'services/pure-base'; | |
import {uploadEmbeddedFile} from 'modules/article-edit/operations.js'; | |
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'; | |
class RichEditor extends PureBase { | |
constructor(props) { | |
super(props); | |
const state = this.convertContentToEditorState(this.props.content); | |
this.state = {editorState: state}; | |
} | |
static propTypes = { | |
onChange: PropTypes.func, | |
content: PropTypes.string, | |
}; | |
onEditorStateChange = editorState => { | |
let content = draftToHtml(convertToRaw(editorState.getCurrentContent())); | |
if(content.indexOf('drop-cap')> -1 ){ | |
if(content.indexOf('drop-cap', content.indexOf('drop-cap') + 8) !== -1){ | |
while(content.indexOf('drop-cap', content.indexOf('drop-cap')+8) !== -1){ | |
const index = content.indexOf('drop-cap', content.indexOf('drop-cap')+8); | |
content = content.substring(0, index - 1) + content.substring(index+8, content.length); | |
} | |
const state = this.convertContentToEditorState(content); | |
this.setState({editorState:state}); | |
} else { | |
this.setState({editorState}); | |
} | |
} else { | |
this.setState({editorState}); | |
} | |
this.props.onChange(content); | |
}; | |
convertContentToEditorState(content){ | |
const blocksFromHtml = htmlToDraft(content || ''); // htmlToDraft is a fork from last file (fork html-to-draft-js/index.js) | |
const {contentBlocks, entityMap} = blocksFromHtml; | |
const contentState = ContentState.createFromBlockArray(contentBlocks, entityMap); | |
return EditorState.createWithContent(contentState); | |
} | |
uploadImageCallBack(file) { | |
return uploadEmbeddedFile('image', file); | |
} | |
uploadVideoCallBack(file) { | |
return uploadEmbeddedFile('video', file); | |
} | |
render() { | |
const {editorState} = this.state; | |
return ( | |
<div> | |
<Editor | |
editorState={editorState} | |
wrapperClassName="RichEditor-wrapper" | |
editorClassName="RichEditor-root" | |
onEditorStateChange={this.onEditorStateChange} | |
placeholder="Enter the text here…" | |
toolbar={{ | |
options: ['blockType', 'inline', 'list', 'link', 'ctaBox', 'ctaImage', 'video', 'remove'], | |
inline: { | |
options: ['bold', 'italic', 'underline', 'superscript', 'subscript'], | |
}, | |
image: { | |
uploadCallback: this.uploadImageCallBack, | |
alt: {present: false, mandatory: false}, | |
previewImage: true, | |
}, | |
video: { | |
uploadCallback: this.uploadVideoCallBack, | |
alt: {present: false, mandatory: false}, | |
previewVideo: true, | |
sound: { | |
src: '/static/img/icon-volume-on.png', | |
text: 'Turn sound on', | |
}, | |
}, | |
ctaImage: { | |
uploadCallback: this.uploadImageCallBack, | |
alt: {present: false, mandatory: false}, | |
previewImage: true, | |
}, | |
blockType: { | |
inDropdown: true, | |
options: ['Normal', 'Header', 'Intro', 'Blockquote', 'Drop Cap'], | |
className: undefined, | |
component: undefined, | |
dropdownClassName: undefined, | |
}, | |
}} | |
/> | |
</div> | |
); | |
} | |
} | |
export default RichEditor; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment