Draft.js

Gore Wang , 2017 @ 線上 React 讀書會

Email:sunrise91.t3@gmail.com

簡介
資料結構
3種裝飾

Agenda

簡介

優點

  • 弭平各家瀏覽器實踐Contenteditable的差異性
  • Contenteditable中更自由實現控制HTML

缺點

  • 專案必定得連帶引入 Immutable.js 
  • 他只能在React上用

資料結構


    // 簡述

    import { EditorState, Editor } from 'draft-js';


    constructor() {

        this.state = {
            editorState: EditorState.createEmpty()
        };

    }


    render() {
        return (
    
            <Editor editorState={ this.state.editorState }/>
    
        )
    }

EditorState

    
    // 實際上是immutable的形式,以下只是方便解說羅列

    EditorState: {
    
        currentContent: ...
        selection: ...
    
        decorator: ...
    
        lastChangeType:"insert-characters"
    
        directionMap: ...
        allowUndo: true
        redoStack: ...
        undoStack: ...
        
        ...
    }

ContentState

    
    // 實際上是immutable的形式,以下只是方便解說羅列

    ContentState: {
    
        blockMap: ...
        entityMap: ...
   
        ...
    }

都~是更新State

我是說所有的事情...

所以才需要 immutable

3 Styling System

  • Inline Style
  • Block
  • Decorator

2 Helper

Inline Styles


    const styleMap = {
        HEIGH_LIGHT: {
            backgroundColor: 'green'
        }
    }

    <Editor customStyleMap={ styleMap }/>
    
    const newEditorState = RichUtils.toggleInlineStyle(editorState, 'HEIGH_LIGHT')

    currentContent:{
        blockMap:{
            blockId: {
                characterList:[
                    {
                        entity:null,
                        style:[]
                    },
                    {
                        entity:null,
                        style:["HEIGH_LIGHT"]
                    }
                ]
            }
        }
    }

會在characterList替所選的每一個字註記style type

Block Styling

註冊Block Type:


    import Immutable from 'immutable';
    
    const BlockRenderMap = Immutable.Map({
      'superTitleBlock': {
        element: 'h1',
        wrapper: <BlockWapperTest/>
      }
    })

    function myBlockStyleFn(contentBlock) {
      const type = contentBlock.getType();
      switch (type) {
        case 'superTitleBlock':
          return 'super__title' // => <h1 class="super__title"...
      }
    }

    <Editor
        blockRenderMap={ extendedBlockRenderMap } 
        blockStyleFn={ myBlockStyleFn } />

    contentBlock: {
        blocks:[
            {
                text: "block styling測試",
                type: "superTitleBlock",
                ...
            }
        ]
    }

    class BlockWapperTest extends Component {
      constructor(props) {
        super(props);
      }
      render() {
        return (
          <div className='MyCustomBlock'>
            { this.props.children }
          </div>
          );
      }
    }

Decorator

  • Regex
  • Entity
  • ...

Strategy:

倚靠指定「匹配範圍」傳入component實踐styling

Decorator Strategy (regex)



    const compositeDecorator = new CompositeDecorator([
        {
            strategy: function(contentBlock, callback, contentState) {
                const text = contentBlock.getText();
                const hashReg = /\#[\w\u0590-\u05ff]+/g;
                let matchArr,
                start;
                while ((matchArr = hashReg.exec(text)) !== null) {
                    start = matchArr.index;
                    callback(start, start + matchArr[0].length);
                }
            },
            component: HashtagSpan,
        }
    ]);

    this.state = {
        editorState: EditorState.createEmpty(compositeDecorator)
    };

Entity


    const contentStateWithEntity = contentState.createEntity(
      'LINK',
      'MUTABLE',
      {href: 'http://www.zombo.com'}
    );

    const entityKey = contentStateWithEntity.getLastCreatedEntityKey();

    const contentStateWithLink = Modifier.applyEntity(
      contentState,
      selectionState,
      entityKey
    );

主要用在創建有帶異動性資料的區塊

(e.g. link url, image url ...)


    currentContent:{
        blockMap:{
            blockId: {
                characterList:[
                    {
                        entity:null,
                        style:[]
                    },
                    {
                        entity:1, // entity id
                        style:[]
                    },
                    ...
                ]
            }
        }
    }

Decorator Strategy (Entity)


    
    const compositeDecorator = new CompositeDecorator([
        {
            strategy: function(contentBlock, callback, contentState) {
              contentBlock.findEntityRanges(
                (character) => {
                  const entityKey = character.getEntity();
                  return (entityKey !== null 
                            && 
                          contentState.getEntity(entityKey).getType() == 'LINK')
                },
                callback)
            },
            component: TestDecoratorWapper
        },
    ]);

Key Bindings


    <Editor
        keyBindingFn={ KeyBindingMap }
        handleKeyCommand={ this.handleKeyCommand }/>
    
    import { getDefaultKeyBinding, KeyBindingUtil } from 'draft-js';
    
    const {hasCommandModifier} = KeyBindingUtil;
    
    function keyBindingFn(e) {
      const hasCommand = hasCommandModifier(e);
      switch (e.keyCode) {
        case ( hasCommand && 83):
          return 'myeditor-save';
    
        default:
          return getDefaultKeyBinding(e);
      }
    
    }
    
    function handleKeyCommand(command) => {
    
        switch (command) {
          case 'myeditor-save':
            console.log('saved');
            return 'handled'
    
          default:
            return 'not-handled'
        }
    
    };
    

  • 使用convertToRaw不只是toJS而已,儲存形式也會跟著改變...
  • DraftJS Plugins beta 2.0.0,有些plugin支援先升了但沒寫...
  • (待更新...)

Thanks~!

Draft.js

By Gore Wang

Draft.js

用React實作自由度超高的Rich Text Editor

  • 2,015