Created
July 18, 2016 15:14
-
-
Save afraser/12d97ee842eeb41bb05dc66fc2c6a9d2 to your computer and use it in GitHub Desktop.
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 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