import { Editor, Mark, Raw, Utils } from '../..' import React from 'react' import initialState from './state.json' import keycode from 'keycode' /** * Define the default node type. */ const DEFAULT_NODE = 'paragraph' /** * Define a set of node renderers. * * @type {Object} */ const NODES = { 'block-quote': (props) =>
{props.children}
, 'bulleted-list': props => , 'heading-one': props =>

{props.children}

, 'heading-two': props =>

{props.children}

, 'list-item': props =>
  • {props.children}
  • , 'numbered-list': props =>
      {props.children}
    } /** * Define a set of mark renderers. * * @type {Object} */ const MARKS = { bold: { fontWeight: 'bold' }, code: { fontFamily: 'monospace', backgroundColor: '#eee', padding: '3px', borderRadius: '4px' }, italic: { fontStyle: 'italic' }, underlined: { textDecoration: 'underline' } } /** * The rich text example. * * @type {Component} */ class RichText extends React.Component { /** * Deserialize the initial editor state. * * @type {Object} */ state = { state: Raw.deserialize(initialState) }; /** * Check if the current selection has a mark with `type` in it. * * @param {String} type * @return {Boolean} */ hasMark = (type) => { const { state } = this.state return state.marks.some(mark => mark.type == type) } /** * Check if the any of the currently selected blocks are of `type`. * * @param {String} type * @return {Boolean} */ hasBlock = (type) => { const { state } = this.state return state.blocks.some(node => node.type == type) } /** * On change, save the new state. * * @param {State} state */ onChange = (state) => { this.setState({ state }) console.log(state.document.toJS()) } /** * On key down, if it's a formatting command toggle a mark. * * @param {Event} e * @param {State} state * @return {State} */ onKeyDown = (e, state) => { if (!Utils.Key.isCommand(e)) return const key = keycode(e.which) let mark switch (key) { case 'b': mark = 'bold' break case 'i': mark = 'italic' break case 'u': mark = 'underlined' break case '`': mark = 'code' break default: return } state = state .transform() [this.hasMark(mark) ? 'removeMark' : 'addMark'](mark) .apply() e.preventDefault() return state } /** * When a mark button is clicked, toggle the current mark. * * @param {Event} e * @param {String} type */ onClickMark = (e, type) => { e.preventDefault() const isActive = this.hasMark(type) let { state } = this.state state = state .transform() [isActive ? 'removeMark' : 'addMark'](type) .apply() this.setState({ state }) } /** * When a block button is clicked, toggle the block type. * * @param {Event} e * @param {String} type */ onClickBlock = (e, type) => { e.preventDefault() let { state } = this.state let transform = state.transform() const { document } = state // Handle everything but list buttons. if (type != 'bulleted-list' && type != 'numbered-list') { const isActive = this.hasBlock(type) transform = transform.setBlock(isActive ? DEFAULT_NODE : type) } // Handle the extra wrapping required for list buttons. else { const isList = this.hasBlock('list-item') const isType = state.blocks.some((block) => { return !!document.getClosest(block, parent => parent.type == type) }) if (isList && isType) { transform = transform .setBlock(DEFAULT_NODE) .unwrapBlock('bulleted-list') .unwrapBlock('numbered-list') } else if (isList) { transform = transform .unwrapBlock(type == 'bulleted-list' ? 'numbered-list' : 'bulleted-list') .wrapBlock(type) } else { transform = transform .setBlock('list-item') .wrapBlock(type) } } state = transform.apply() this.setState({ state }) } /** * Render. * * @return {Element} */ render = () => { return (
    {this.renderToolbar()} {this.renderEditor()}
    ) } /** * Render the toolbar. * * @return {Element} */ renderToolbar = () => { return (
    {this.renderMarkButton('bold', 'format_bold')} {this.renderMarkButton('italic', 'format_italic')} {this.renderMarkButton('underlined', 'format_underlined')} {this.renderMarkButton('code', 'code')} {this.renderBlockButton('heading-one', 'looks_one')} {this.renderBlockButton('heading-two', 'looks_two')} {this.renderBlockButton('block-quote', 'format_quote')} {this.renderBlockButton('numbered-list', 'format_list_numbered')} {this.renderBlockButton('bulleted-list', 'format_list_bulleted')}
    ) } /** * Render a mark-toggling toolbar button. * * @param {String} type * @param {String} icon * @return {Element} */ renderMarkButton = (type, icon) => { const isActive = this.hasMark(type) const onMouseDown = e => this.onClickMark(e, type) return ( {icon} ) } /** * Render a block-toggling toolbar button. * * @param {String} type * @param {String} icon * @return {Element} */ renderBlockButton = (type, icon) => { const isActive = this.hasBlock(type) const onMouseDown = e => this.onClickBlock(e, type) return ( {icon} ) } /** * Render the Slate editor. * * @return {Element} */ renderEditor = () => { return (
    ) } /** * Return a node renderer for a Slate `node`. * * @param {Node} node * @return {Component or Void} */ renderNode = (node) => { return NODES[node.type] } /** * Return a mark renderer for a Slate `mark`. * * @param {Mark} mark * @return {Object or Void} */ renderMark = (mark) => { return MARKS[mark.type] } } /** * Export. */ export default RichText