Skip to content

Instantly share code, notes, and snippets.

@afraser
Created July 18, 2016 15:14
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save afraser/12d97ee842eeb41bb05dc66fc2c6a9d2 to your computer and use it in GitHub Desktop.
Save afraser/12d97ee842eeb41bb05dc66fc2c6a9d2 to your computer and use it in GitHub Desktop.
import React, { Component, PropTypes } from 'react'
import Toolbar from 'containers/Toolbar'
import InlineMath, { forceUpdateEquation } from 'containers/InlineMath'
import EquationEditor from 'components/EquationEditor'
import { Editor, EditorState, ContentState, SelectionState, Entity, CompositeDecorator, Modifier, convertToRaw, RichUtils } from 'draft-js'
const SUPPORTED_COMMANDS = [ 'italic' ]
function findTex(contentBlock, callback) {
contentBlock.findEntityRanges(
(character) => {
const key = character.getEntity()
return key !== null && Entity.get(key).getType() === 'equation'
},
callback
)
}
export default class BoundlessEditor extends Component {
static propTypes = {
contentState: PropTypes.instanceOf(ContentState).isRequired,
editorProps: PropTypes.object,
media: PropTypes.array,
figuresCdn: PropTypes.string,
}
state = {
editorState: EditorState.createWithContent(this.props.contentState),
showEquationEdit: false,
currentEquationEntityKey: null,
currentTextSelection: null,
addedEolHack: false
}
onChange(editorState) {
this.setState({addedEolHack: false})
const addEntityEolDelimiter = (editorState, block) => {
const blockKey = block.key
const characterList = block.characterList
if ((!characterList.isEmpty() && characterList.last().getEntity())) {
if(editorState.getLastChangeType() === 'backspace-character' && this.state.addedEolHack) {
const selection = new SelectionState({
anchorKey: blockKey,
anchorOffset: block.getLength() - 1,
focusKey: blockKey,
focusOffset: block.getLength(),
hasFocus: true
})
const modifiedContent = Modifier.removeRange(editorState.getCurrentContent(), selection, 'backward')
return EditorState.push(editorState, modifiedContent, editorState.getLastChangeType())
} else {
const selection = new SelectionState({
anchorKey: blockKey,
anchorOffset: block.getLength(),
focusKey: blockKey,
focusOffset: block.getLength(),
hasFocus: true
})
this.setState({addedEolHack: true})
const zwwsp = String.fromCharCode(8203)
const modifiedContent = Modifier.insertText(editorState.getCurrentContent(), selection, zwwsp)
return EditorState.push(editorState, modifiedContent, editorState.getLastChangeType())
}
} else {
return editorState
}
}
if(editorState.getLastChangeType()==='undo' || editorState.getLastChangeType()==='redo'){
this.setState({editorState})
}else{
const currentContent = editorState.getCurrentContent()
const blocks = currentContent.blockMap
const newEditorState = blocks.reduce(addEntityEolDelimiter, editorState)
this.setState({ editorState: newEditorState })
}
}
handleKeyCommand(command) {
if (_.includes(SUPPORTED_COMMANDS, command)) {
const newState = RichUtils.handleKeyCommand(this.state.editorState, command)
if (newState) {
this.onChange(newState)
return true
}
}
return false
}
handleEquationClicked(entityKey) {
this.setState({
showEquationEdit: true,
currentEquationEntityKey: entityKey
})
}
componentWillMount() {
let compositeDecorator = new CompositeDecorator([{
strategy: findTex,
component: InlineMath,
props: {
onClick: ::this.handleEquationClicked
}
}])
const decoratedState = EditorState.set(this.state.editorState, {decorator: compositeDecorator})
this.setState({ editorState: decoratedState })
}
handleMedia(entityKey, entity) {
const mediaId = entity.getData()["mediaId"]
return {
component: Media,
editable: false,
props: {
entityKey,
figuresCdn: this.props.figuresCdn,
media: _.find(this.props.media, (media) => media.id == mediaId)
},
}
}
customBlockRenderer(contentBlock) {
const entityKey = contentBlock.getEntityAt(0)
if (entityKey !== null) {
const entity = Entity.get(entityKey)
switch(entity.getType()) {
case "image":
return this.handleMedia(entityKey, entity)
}
}
}
handleClickedInsertEquation() {
const editorState = this.state.editorState
const currentContent = editorState.getCurrentContent()
const selection = editorState.getSelection();
const contentBlock = currentContent.getBlockMap().get(selection.getFocusKey())
const start = selection.getStartOffset()
const end = selection.getEndOffset();
const selectedText = contentBlock.getText().slice(start, end);
this.setState({
showEquationEdit: true,
currentEquationEntityKey: null,
currentTextSelection: selectedText
})
}
insertEquation(tex) {
const editorState = this.state.editorState
const currentContent = editorState.getCurrentContent()
const entity = Entity.create('equation', 'IMMUTABLE', { text: tex })
const selection = editorState.getSelection()
const textWithEntity = Modifier.replaceText(currentContent, selection, " ", null, entity)
this.setState({
editorState: EditorState.push(editorState, textWithEntity, "insert-characters")
})
}
handleSaveEquation(tex) {
const { currentEquationEntityKey } = this.state
if (currentEquationEntityKey) {
// The user is editing an equation that's already in the editor
Entity.mergeData(currentEquationEntityKey, {text: tex})
forceUpdateEquation(currentEquationEntityKey)
} else {
// The user is editing a new equation
this.insertEquation(tex)
}
this.hideEquationEditor()
}
hideEquationEditor() {
this.setState({
showEquationEdit: false,
currentEquationEntityKey: null,
})
}
logState() {
console.log('props', this.state.editorState.toJS())
console.log('state', convertToRaw(this.state.editorState.getCurrentContent()))
}
toggleBlockStyle(blockType) {
this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType))
}
toggleInlineStyle(style) {
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, style))
}
render() {
const { editorProps } = this.props
const { editorState, currentEquationEntityKey } = this.state
const styleMap = {
superscript: {
verticalAlign: 'super',
fontSize: '1rem',
}
}
let equationEditor
if (this.state.showEquationEdit) {
equationEditor = (
<EquationEditor
initEquation={this.state.currentTextSelection}
entityKey={currentEquationEntityKey}
onSubmit={::this.handleSaveEquation}
onCancel={::this.hideEquationEditor} />
)
}
return (
<div>
<Toolbar
withBlockControls={this.props.withBlockControls}
onToggleInlineStyle={::this.toggleInlineStyle}
onToggleBlockStyle={::this.toggleBlockStyle}
onInsertEquation={::this.handleClickedInsertEquation}
editorState={editorState}/>
{ equationEditor }
<Editor
blockRendererFn={::this.customBlockRenderer}
editorState={editorState}
customStyleMap={styleMap}
onChange={::this.onChange}
handleKeyCommand={::this.handleKeyCommand}
{...editorProps} />
<button onClick={::this.logState}>Log State</button>
</div>
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment