import * as React from "react"; import * as ReactDOM from "react-dom"; import * as Immutable from 'immutable'; import {Editor, Entity, EditorState, RichUtils, AtomicBlockUtils, EditorBlock, ContentBlock, ContentState, CompositeDecorator, Modifier} from 'draft-js'; import * as Draft from 'draft-js'; import {C, Cont, CmdCommon, Mode, make_C, unit, bind} from './core' import * as katex from "katex"; type DraftEditorCommand = "undo" | "redo" | "delete" | "delete-word" | "backspace" | "backspace-word" | "backspace-to-start-of-line" | "bold" | "italic" | "underline" | "link" | "code" | "split-block" | "transpose-characters" | "move-selection-to-start-of-block" | "move-selection-to-end-of-block" | "secondary-cut" | "secondary-paste" type DraftBlockType = "unstyled" | "paragraph" | "header-one" | "header-two" | "header-three" | "header-four" | "header-five" | "header-six" | "unordered-list-item" | "ordered-list-item" | "blockquote" | "code-block" | "atomic" type DraftState = { editor_state: EditorState } type DraftProps = { initial_state: EditorState, set_state: (s:EditorState, on_success?: () => void) => void, editable: boolean } class DraftEditor extends React.Component { constructor(props:DraftProps, context) { super(props, context) this.state = { editor_state: this.props.initial_state } } static decorator = () => new CompositeDecorator([ { strategy: DraftEditor.findLinkentities, component: Link, }, ]); static serialize_state(editor_state:EditorState) : string { return JSON.stringify(Draft.convertToRaw(editor_state.getCurrentContent())) } static deserialize_state(raw_content:any) : EditorState { try { return EditorState.createWithContent(Draft.convertFromRaw(JSON.parse(raw_content)), DraftEditor.decorator()) } catch (e) { return DraftEditor.empty_state() } } static findLinkentities = (contentBlock, callback) => { contentBlock.findEntityRanges( (character) => { const entityKey = character.getEntity(); return ( entityKey !== null && Entity.get(entityKey).getType() === 'LINK' ); }, callback ); } static empty_state() : EditorState { return EditorState.createEmpty(DraftEditor.decorator()) } onChange(new_editor_state:EditorState, on_success?: () => void) { if (this.props.editable) { this.setState({...this.state, editor_state: new_editor_state}, () => { if (on_success) on_success() this.props.set_state(new_editor_state) }) } } toggle_block_type(block_type:DraftBlockType) { this.onChange( RichUtils.toggleBlockType( this.state.editor_state, block_type ), () => this.editor.focus() ) } toggle_style(command:DraftEditorCommand) { this.handleKeyCommand(command) } handleKeyCommand(command:DraftEditorCommand) { let new_state = RichUtils.handleKeyCommand(this.state.editor_state, command); if (new_state) { this.onChange(new_state, () => { this.editor.focus() }) return "handled" } return "not-handled" } insert_media(contentState: Draft.ContentState, url:string, url_type:MediaType) { let selectionState = this.state.editor_state.getSelection() let new_content_state = contentState.createEntity(url_type, 'IMMUTABLE', {src: url}) let entity_key = new_content_state.getLastCreatedEntityKey() let new_editor_state = AtomicBlockUtils.insertAtomicBlock(this.state.editor_state, entity_key, ' ') // new_content_state = new_editor_state.getCurrentContent() // var anchorKey = selectionState.getAnchorKey(); // var currentContentBlock = new_content_state.getBlockForKey(anchorKey) // let blockMap = new_content_state.getBlockMap() // let newBlockMap = currentContentBlock.getText() == "" ? blockMap.remove(currentContentBlock.getKey()) : blockMap // const newContentState = contentState.set('blockMap', newBlockMap) as ContentState; // let newEditorState = EditorState.createWithContent(newContentState) let newEditorState = new_editor_state this.setState({...this.state, editor_state: newEditorState}, () => { this.props.set_state(new_editor_state) }) } insert_link(contentState: Draft.ContentState, url:string) { let contentStateWithEntity = contentState.createEntity('LINK', 'IMMUTABLE', {url: url}) let entityKey = contentStateWithEntity.getLastCreatedEntityKey() let newEditorState = RichUtils.toggleLink( this.state.editor_state, this.state.editor_state.getSelection(), entityKey ) this.setState({...this.state, editor_state: newEditorState}, () => { this.props.set_state(newEditorState) }) } editor: Editor = null render() { return (
{this.props.editable ? this.toggle_style(s)} toggle_block_type={(s:DraftBlockType) => this.toggle_block_type(s)} insert_media={(url:string, url_type:MediaType) => this.insert_media(this.state.editor_state.getCurrentContent() ,url, url_type)} insert_link={(url: string) => this.insert_link(this.state.editor_state.getCurrentContent(), url)} /> : null}
{}} onChange={es => this.onChange(es)} handleKeyCommand={(c:DraftEditorCommand) => this.handleKeyCommand(c)} readOnly={!this.props.editable} blockRendererFn={mediaBlockRenderer(this.state.editor_state.getCurrentContent(), this.props.editable)} ref={(editor) => this.editor = editor } spellCheck={true} />
) } } function mediaBlockRenderer(contentState: Draft.ContentState ,editable:boolean) { return (block:ContentBlock) => { if (block.getType() === 'atomic') { return { component: Media(editable), editable: false, }; } return null; } } type MathProps = { src: string, editable: boolean, contentState: ContentState, block: ContentBlock } class Math extends React.Component { constructor(props) { super(props) } onClick() { if (!this.props.editable) { return } let block = this.props.block let contentState = this.props.contentState let newTex = prompt("Enter your tex here", this.props.src) || this.props.src let entityKey = block.getEntityAt(0) contentState.mergeEntityData(entityKey, { src: newTex }) } render() { return
this.onClick()} />
} } type MathOutputProps = { content: string, onClick: () => void } class MathOutput extends React.Component { constructor(props) { super(props) } _timer = null; _container: HTMLElement = null; _update() { if (this._timer) { clearTimeout(this._timer); } this._timer = setTimeout(() => { katex.render( this.props.content, this._container, { displayMode: false } ) }, 0); } componentDidMount() { this._update() } componentWillReceiveProps(props) { if (props.src !== this.props.content) { this._update(); } } componentWillUnmount() { clearTimeout(this._timer) this._timer = null } render() { return this._container = c} onClick={this.props.onClick}/> } } const Image = (props:{src:string}) => { return ; }; const Video = (props:{src:string}) => { return