client/src/components/SlateEditor.js
changeset 157 5c3af4f10e92
parent 143 cfcbf4bc66f1
child 159 a4705c2b4544
equal deleted inserted replaced
156:384f4539b76a 157:5c3af4f10e92
     1 import { Editor, Plain, Raw } from 'slate'
     1 import { Value } from 'slate'
       
     2 import Plain from 'slate-plain-serializer'
       
     3 import { Editor } from 'slate-react'
     2 import React from 'react'
     4 import React from 'react'
     3 import Portal from 'react-portal'
     5 import Portal from 'react-portal'
     4 import Immutable from 'immutable'
     6 import Immutable from 'immutable'
     5 import HtmlSerializer from '../HtmlSerializer'
     7 import HtmlSerializer from '../HtmlSerializer'
     6 import AnnotationPlugin from '../AnnotationPlugin'
     8 import AnnotationPlugin from '../AnnotationPlugin'
    39     },
    41     },
    40     italic: {
    42     italic: {
    41       fontStyle: 'italic'
    43       fontStyle: 'italic'
    42     },
    44     },
    43     underlined: {
    45     underlined: {
    44       textDecoration: 'underline'
    46       textDecoration: 'underlined'
    45     }
    47     }
    46   }
    48   }
    47 }
    49 }
       
    50 
       
    51 const initialValue = Value.fromJSON({
       
    52   document: {
       
    53     nodes: [
       
    54       {
       
    55         object: 'block',
       
    56         type: 'paragraph',
       
    57         nodes: [
       
    58           {
       
    59             object: 'text',
       
    60             leaves: [
       
    61               {
       
    62                 text: '',
       
    63               },
       
    64             ],
       
    65           },
       
    66         ],
       
    67       },
       
    68     ],
       
    69   },
       
    70 })
    48 
    71 
    49 /**
    72 /**
    50  * The rich text example.
    73  * The rich text example.
    51  *
    74  *
    52  * @type {Component}
    75  * @type {Component}
    72       }
    95       }
    73     });
    96     });
    74 
    97 
    75     plugins.push(annotationPlugin);
    98     plugins.push(annotationPlugin);
    76 
    99 
       
   100 
    77     this.state = {
   101     this.state = {
    78       state: props.note ? Raw.deserialize(props.note.raw) : Plain.deserialize(''),
   102       value: props.note ? initialValue : Plain.deserialize(''),
    79       startedAt: null,
   103       startedAt: null,
    80       finishedAt: null,
   104       finishedAt: null,
    81       currentSelectionText: '',
   105       currentSelectionText: '',
    82       currentSelectionStart: 0,
   106       currentSelectionStart: 0,
    83       currentSelectionEnd: 0,
   107       currentSelectionEnd: 0,
    95 
   119 
    96   componentDidUpdate = () => {
   120   componentDidUpdate = () => {
    97     this.updateMenu();
   121     this.updateMenu();
    98   }
   122   }
    99 
   123 
   100   /**
   124    /**
   101    * Check if the current selection has a mark with `type` in it.
       
   102    *
       
   103    * @param {String} type
       
   104    * @return {Boolean}
       
   105    */
       
   106 
       
   107   hasMark = (type) => {
       
   108     const { state } = this.state
       
   109     return state.marks.some(mark => mark.type === type)
       
   110   }
       
   111 
       
   112   /**
       
   113    * Check if the any of the currently selected blocks are of `type`.
       
   114    *
       
   115    * @param {String} type
       
   116    * @return {Boolean}
       
   117    */
       
   118 
       
   119   hasBlock = (type) => {
       
   120     const { state } = this.state
       
   121     return state.blocks.some(node => node.type === type)
       
   122   }
       
   123 
       
   124   /**
       
   125    * On change, save the new state.
   125    * On change, save the new state.
   126    *
   126    *
   127    * @param {State} state
   127    * @param {Change} change
   128    */
   128    */
   129 
   129 
   130   onChange = (state) => {
   130   onChange = ({value}) => {
   131 
   131 
   132     let newState = {
   132     let newState = {
   133       state: state,
   133       value: value,
   134       startedAt: this.state.startedAt
   134       startedAt: this.state.startedAt
   135     };
   135     };
   136 
   136 
   137     const isEmpty = state.document.length === 0;
   137     const isEmpty = value.document.length === 0;
   138 
   138 
   139     // Reset timers when the text is empty
   139     // Reset timers when the text is empty
   140     if (isEmpty) {
   140     if (isEmpty) {
   141       Object.assign(newState, {
   141       Object.assign(newState, {
   142         startedAt: null,
   142         startedAt: null,
   156     if (typeof this.props.onChange === 'function') {
   156     if (typeof this.props.onChange === 'function') {
   157       this.props.onChange(newState);
   157       this.props.onChange(newState);
   158     }
   158     }
   159   }
   159   }
   160 
   160 
       
   161   /**
       
   162    * Check if the current selection has a mark with `type` in it.
       
   163    *
       
   164    * @param {String} type
       
   165    * @return {Boolean}
       
   166    */
       
   167 
       
   168   hasMark = type => {
       
   169     const { value } = this.state
       
   170     return value.activeMarks.some(mark => mark.type === type)
       
   171 }
       
   172 
       
   173   /**
       
   174    * Check if the any of the currently selected blocks are of `type`.
       
   175    *
       
   176    * @param {String} type
       
   177    * @return {Boolean}
       
   178    */
       
   179 
       
   180   hasBlock = type => {
       
   181     const { value } = this.state
       
   182     return value.blocks.some(node => node.type === type)
       
   183 }
       
   184 
   161   asPlain = () => {
   185   asPlain = () => {
   162     return Plain.serialize(this.state.state);
   186     return Plain.serialize(this.state.value);
   163   }
   187   }
   164 
   188 
   165   asRaw = () => {
   189   asRaw = () => {
   166     return Raw.serialize(this.state.state);
   190     return JSON.stringify(this.state.value.toJSON());
   167   }
   191   }
   168 
   192 
   169   asHtml = () => {
   193   asHtml = () => {
   170     return HtmlSerializer.serialize(this.state.state);
   194     return HtmlSerializer.serialize(this.state.value);
   171   }
   195   }
   172 
   196 
   173   asCategories = () => {
   197   asCategories = () => {
   174     return this.state.categories
   198     return this.state.categories
   175   }
   199   }
   178     const categoryIndex = categories.findIndex(category => category.key === key && category.text === text)
   202     const categoryIndex = categories.findIndex(category => category.key === key && category.text === text)
   179     return categories.delete(categoryIndex)
   203     return categories.delete(categoryIndex)
   180   }
   204   }
   181 
   205 
   182   clear = () => {
   206   clear = () => {
   183     const state = Plain.deserialize('');
   207     const value = Plain.deserialize('');
   184     this.onChange(state);
   208     this.onChange({value});
   185   }
   209   }
   186 
   210 
   187   focus = () => {
   211   focus = () => {
   188     this.refs.editor.focus();
   212     this.refs.editor.focus();
   189   }
   213   }
   191   /**
   215   /**
   192    * On key down, if it's a formatting command toggle a mark.
   216    * On key down, if it's a formatting command toggle a mark.
   193    *
   217    *
   194    * @param {Event} e
   218    * @param {Event} e
   195    * @param {Object} data
   219    * @param {Object} data
   196    * @param {State} state
   220    * @param {Change} change
   197    * @return {State}
   221    * @return {Change}
   198    */
   222    */
   199 
   223 
   200   onKeyDown = (e, data, state) => {
   224   onKeyDown = (e, change) => {
   201 
   225     //   if (data.key === 'enter' && this.props.isChecked && typeof this.props.onEnterKeyDown === 'function') {
   202     if (data.key === 'enter' && this.props.isChecked && typeof this.props.onEnterKeyDown === 'function') {
   226 
   203       e.preventDefault();
   227     //   e.preventDefault();
   204       this.props.onEnterKeyDown();
   228     //   this.props.onEnterKeyDown();
   205 
   229 
   206       return state;
   230     //   return change;
   207     }
   231     // }
   208 
   232 
   209     if (!data.isMod) return
   233     // if (!data.isMod) return
   210     let mark
   234     if (!e.ctrlKey) return
   211 
   235         // Decide what to do based on the key code...
   212     switch (data.key) {
   236         switch (e.key) {
   213       case 'b':
   237           // When "B" is pressed, add a "bold" mark to the text.
   214         mark = 'bold'
   238           case 'b': {
   215         break
   239             e.preventDefault()
   216       case 'i':
   240             change.toggleMark('bold')
   217         mark = 'italic'
   241 
   218         break
   242             return true
   219       case 'u':
   243           }
   220         mark = 'underlined'
   244           case 'i': {
   221         break
   245             // When "U" is pressed, add an "italic" mark to the text.
   222       default:
   246             e.preventDefault()
   223         return
   247             change.toggleMark('italic')
   224     }
   248 
   225 
   249             return true
   226     state = state
   250           }
   227       .transform()
   251           case 'u': {
   228       .toggleMark(mark)
   252             // When "U" is pressed, add an "underline" mark to the text.
   229       .apply()
   253             e.preventDefault()
   230 
   254             change.toggleMark('underlined')
   231     e.preventDefault()
   255 
   232     return state
   256             return true
   233   }
   257           }
   234 
   258         }
   235   /**
   259     }
       
   260 
       
   261       /**
   236    * When a mark button is clicked, toggle the current mark.
   262    * When a mark button is clicked, toggle the current mark.
   237    *
   263    *
   238    * @param {Event} e
   264    * @param {Event} e
   239    * @param {String} type
   265    * @param {String} type
   240    */
   266    */
   241 
   267 
   242   onClickMark = (e, type) => {
   268   onClickMark = (e, type) => {
   243     e.preventDefault()
   269 
   244     const { state } = this.state
   270       e.preventDefault()
       
   271     const { value } = this.state
   245     let { categories } = this.state
   272     let { categories } = this.state
   246     const transform = state.transform()
       
   247 
   273 
   248     let isPortalOpen = false;
   274     let isPortalOpen = false;
   249 
   275 
   250     if (type === 'category') {
   276     if (type === 'category') {
   251       // Can't use toggleMark here, because it expects the same object
   277       // Can't use toggleMark here, because it expects the same object
   252       // @see https://github.com/ianstormtaylor/slate/issues/873
   278       // @see https://github.com/ianstormtaylor/slate/issues/873
   253       if (this.hasMark('category')) {
   279       if (this.hasMark('category')) {
   254         const categoryMarks = state.marks.filter(mark => mark.type === 'category')
   280         const categoryMarks = value.activeMarks.filter(mark => mark.type === 'category')
   255         categoryMarks.forEach(mark => {
   281         categoryMarks.forEach(mark => {
   256           const key = mark.data.get('key');
   282           const key = mark.data.get('key');
   257           const text = mark.data.get('text');
   283           const text = mark.data.get('text');
   258 
   284 
   259           categories = this.removeCategory(categories, key, text)
   285           categories = this.removeCategory(categories, key, text)
   260           transform.removeMark(mark)
   286           const change = value.change().removeMark(mark)
       
   287           this.onChange(change)
   261         })
   288         })
   262 
   289 
   263       } else {
   290       } else {
   264         isPortalOpen = !this.state.isPortalOpen;
   291         isPortalOpen = !this.state.isPortalOpen;
   265       }
   292       }
   266     } else {
   293     } else {
   267       transform.toggleMark(type)
   294       const change = value.change().toggleMark(type)
       
   295       this.onChange(change)
   268     }
   296     }
   269 
   297 
   270     this.setState({
   298     this.setState({
   271       state: transform.apply(),
   299       state: value.change,
   272       isPortalOpen: isPortalOpen,
   300       isPortalOpen: isPortalOpen,
   273       categories: categories
   301       categories: categories
   274     })
   302     })
   275   }
   303   }
   276 
   304 
   281    * @param {String} type
   309    * @param {String} type
   282    */
   310    */
   283 
   311 
   284   onClickBlock = (e, type) => {
   312   onClickBlock = (e, type) => {
   285     e.preventDefault()
   313     e.preventDefault()
   286     let { state } = this.state
   314     const { value } = this.state
   287     const transform = state.transform()
   315     const change = value.change()
   288     const { document } = state
   316     const { document } = value
   289 
   317 
   290     // Handle everything but list buttons.
   318     // Handle everything but list buttons.
   291     if (type !== 'bulleted-list' && type !== 'numbered-list') {
   319     if (type !== 'bulleted-list' && type !== 'numbered-list') {
   292       const isActive = this.hasBlock(type)
   320       const isActive = this.hasBlock(type)
   293       const isList = this.hasBlock('list-item')
   321       const isList = this.hasBlock('list-item')
   294 
   322 
   295       if (isList) {
   323       if (isList) {
   296         transform
   324         change
   297           .setBlock(isActive ? DEFAULT_NODE : type)
   325           .setBlocks(isActive ? DEFAULT_NODE : type)
   298           .unwrapBlock('bulleted-list')
   326           .unwrapBlock('bulleted-list')
   299           .unwrapBlock('numbered-list')
   327           .unwrapBlock('numbered-list')
   300       }
   328       }
   301 
   329 
   302       else {
   330       else {
   303         transform
   331        change
   304           .setBlock(isActive ? DEFAULT_NODE : type)
   332           .setBlocks(isActive ? DEFAULT_NODE : type)
   305       }
   333       }
   306     }
   334     }
   307 
   335 
   308     // Handle the extra wrapping required for list buttons.
   336     // Handle the extra wrapping required for list buttons.
   309     else {
   337     else {
   310       const isList = this.hasBlock('list-item')
   338       const isList = this.hasBlock('list-item')
   311       const isType = state.blocks.some((block) => {
   339       const isType = value.blocks.some((block) => {
   312         return !!document.getClosest(block.key, parent => parent.type === type)
   340         return !!document.getClosest(block.key, parent => parent.type === type)
   313       })
   341       })
   314 
   342 
   315       if (isList && isType) {
   343       if (isList && isType) {
   316         transform
   344        change
   317           .setBlock(DEFAULT_NODE)
   345           .setBlocks(DEFAULT_NODE)
   318           .unwrapBlock('bulleted-list')
   346           .unwrapBlock('bulleted-list')
   319           .unwrapBlock('numbered-list')
   347           .unwrapBlock('numbered-list')
       
   348 
   320       } else if (isList) {
   349       } else if (isList) {
   321         transform
   350         change
   322           .unwrapBlock(type === 'bulleted-list' ? 'numbered-list' : 'bulleted-list')
   351           .unwrapBlock(type === 'bulleted-list' ? 'numbered-list' : 'bulleted-list')
   323           .wrapBlock(type)
   352           .wrapBlock(type)
       
   353 
   324       } else {
   354       } else {
   325         transform
   355         change
   326           .setBlock('list-item')
   356           .setBlocks('list-item')
   327           .wrapBlock(type)
   357           .wrapBlock(type)
       
   358 
   328       }
   359       }
   329     }
   360     }
   330 
   361 
   331     state = transform.apply()
   362 
   332     this.setState({ state })
   363     this.onChange(change)
   333   }
   364   }
   334 
   365 
   335   onPortalOpen = (portal) => {
   366   onPortalOpen = (portal) => {
   336     // When the portal opens, cache the menu element.
   367     // When the portal opens, cache the menu element.
   337     this.setState({ hoveringMenu: portal.firstChild })
   368     this.setState({ hoveringMenu: portal.firstChild })
   338   }
   369   }
   339 
   370 
   340   onPortalClose = (portal) => {
   371   onPortalClose = (portal) => {
   341     let { state } = this.state
   372     let { value } = this.state
   342     const transform = state.transform();
       
   343 
   373 
   344     this.setState({
   374     this.setState({
   345       state: transform.apply(),
   375       value: value.change,
   346       isPortalOpen: false
   376       isPortalOpen: false
   347     })
   377     })
   348   }
   378   }
   349 
   379 
   350   onCategoryClick = (category) => {
   380   onCategoryClick = (category) => {
   351 
   381 
   352     const { state, currentSelectionText, currentSelectionStart, currentSelectionEnd } = this.state;
   382     const { value, currentSelectionText, currentSelectionStart, currentSelectionEnd } = this.state;
       
   383     const change = value.change()
   353     let { categories } = this.state;
   384     let { categories } = this.state;
   354     const transform = state.transform();
   385 
   355 
   386     const categoryMarks = value.activeMarks.filter(mark => mark.type === 'category')
   356     const categoryMarks = state.marks.filter(mark => mark.type === 'category')
   387     categoryMarks.forEach(mark => change.removeMark(mark));
   357     categoryMarks.forEach(mark => transform.removeMark(mark));
   388 
   358 
   389     change.addMark({
   359     transform.addMark({
       
   360       type: 'category',
   390       type: 'category',
   361       data: {
   391       data: {
   362         text: currentSelectionText,
   392         text: currentSelectionText,
   363         selection: {
   393         selection: {
   364           start: currentSelectionStart,
   394           start: currentSelectionStart,
   366         },
   396         },
   367         color: category.color,
   397         color: category.color,
   368         key: category.key
   398         key: category.key
   369       }
   399       }
   370     })
   400     })
       
   401     this.onChange(change)
   371 
   402 
   372     Object.assign(category, {
   403     Object.assign(category, {
   373       text: currentSelectionText,
   404       text: currentSelectionText,
   374       selection: {
   405       selection: {
   375         start: currentSelectionStart,
   406         start: currentSelectionStart,
   377       },
   408       },
   378     });
   409     });
   379     categories = categories.push(category);
   410     categories = categories.push(category);
   380 
   411 
   381     this.setState({
   412     this.setState({
   382       state: transform.apply(),
   413       value: value,
   383       isPortalOpen: false,
   414       isPortalOpen: false,
   384       categories: categories
   415       categories: categories
   385     });
   416     });
   386   }
   417   }
   387 
   418 
   403    * @return {Element}
   434    * @return {Element}
   404    */
   435    */
   405 
   436 
   406   render = () => {
   437   render = () => {
   407     return (
   438     return (
   408       <div>
   439       <div className="bg-secondary mb-5">
       
   440         <div className="sticky-top">
   409         {this.renderToolbar()}
   441         {this.renderToolbar()}
       
   442         </div>
   410         {this.renderEditor()}
   443         {this.renderEditor()}
       
   444     </div>
       
   445     )
       
   446   }
       
   447 
       
   448   /**
       
   449    * Render the toolbar.
       
   450    *
       
   451    * @return {Element}
       
   452    */
       
   453 
       
   454   renderToolbar = () => {
       
   455     return (
       
   456       <div className="menu toolbar-menu d-flex bg-secondary">
       
   457           {this.renderMarkButton('bold', 'format_bold')}
       
   458           {this.renderMarkButton('italic', 'format_italic')}
       
   459           {this.renderMarkButton('underlined', 'format_underlined')}
       
   460           {this.renderMarkButton('category', 'label')}
       
   461 
       
   462 
       
   463           {this.renderBlockButton('numbered-list', 'format_list_numbered')}
       
   464           {this.renderBlockButton('bulleted-list', 'format_list_bulleted')}
       
   465 
       
   466           {this.renderToolbarButtons()}
   411       </div>
   467       </div>
   412     )
   468     )
   413   }
   469   }
   414 
   470 
   415   /**
       
   416    * Render the toolbar.
       
   417    *
       
   418    * @return {Element}
       
   419    */
       
   420 
       
   421   renderToolbar = () => {
       
   422     return (
       
   423       <div className="menu toolbar-menu">
       
   424         {this.renderMarkButton('bold', 'format_bold')}
       
   425         {this.renderMarkButton('italic', 'format_italic')}
       
   426         {this.renderMarkButton('underlined', 'format_underlined')}
       
   427         {this.renderMarkButton('category', 'label')}
       
   428 
       
   429         {this.renderBlockButton('numbered-list', 'format_list_numbered')}
       
   430         {this.renderBlockButton('bulleted-list', 'format_list_bulleted')}
       
   431 
       
   432         {this.renderToolbarButtons()}
       
   433       </div>
       
   434     )
       
   435   }
       
   436 
       
   437   renderToolbarCheckbox = () => {
   471   renderToolbarCheckbox = () => {
   438     return (
   472     return (
   439       <div className="checkbox">
   473       <div className="checkbox float-right">
   440         <label>
   474         <label className="mr-2">
   441           <input type="checkbox" checked={this.props.isChecked} onChange={this.onCheckboxChange} /> <kbd>Entrée</kbd>= Ajouter une note
   475           <input type="checkbox" checked={this.props.isChecked} onChange={this.onCheckboxChange} /><small className="text-muted ml-1"> Appuyer sur <kbd className="bg-danger text-muted ml-1">Entrée</kbd> pour ajouter une note</small>
   442         </label>
   476         </label>
   443       </div>
   477       </div>
   444     )
   478     )
   445   }
   479   }
   446 
   480 
   447   renderToolbarButtons = () => {
   481   renderToolbarButtons = () => {
   448     return (
   482     return (
   449       <div>
   483       <div>
   450         { !this.props.note && this.renderToolbarCheckbox() }
   484         <button type="button" className="btn btn-primary btn-sm text-secondary float-right mr-5" disabled={this.props.isButtonDisabled} onClick={this.onButtonClick}>
   451         <button type="button" className="btn btn-primary btn-lg" disabled={this.props.isButtonDisabled} onClick={this.onButtonClick}>
       
   452           { this.props.note ? 'Save note' : 'Ajouter' }
   485           { this.props.note ? 'Save note' : 'Ajouter' }
   453         </button>
   486         </button>
       
   487         { !this.props.note && this.renderToolbarCheckbox() }
   454       </div>
   488       </div>
   455     );
   489     );
   456   }
   490   }
   457 
   491 
   458   /**
   492   /**
   465 
   499 
   466   renderMarkButton = (type, icon) => {
   500   renderMarkButton = (type, icon) => {
   467     const isActive = this.hasMark(type)
   501     const isActive = this.hasMark(type)
   468     const onMouseDown = e => this.onClickMark(e, type)
   502     const onMouseDown = e => this.onClickMark(e, type)
   469 
   503 
   470     return (
   504 
   471       <span className="button" onMouseDown={onMouseDown} data-active={isActive}>
   505     return (
       
   506       <span className="button text-primary" onMouseDown={onMouseDown} data-active={isActive}>
   472         <span className="material-icons">{icon}</span>
   507         <span className="material-icons">{icon}</span>
   473       </span>
   508       </span>
   474     )
   509     )
   475   }
   510   }
   476 
   511 
       
   512     // Add a `renderMark` method to render marks.
       
   513 
       
   514     renderMark = props => {
       
   515       const { children, mark, attributes } = props
       
   516 
       
   517       switch (mark.type) {
       
   518         case 'bold':
       
   519           return <strong {...attributes}>{children}</strong>
       
   520         case 'code':
       
   521           return <code {...attributes}>{children}</code>
       
   522         case 'italic':
       
   523           return <em {...attributes}>{children}</em>
       
   524         case 'underlined':
       
   525           return <ins {...attributes}>{children}</ins>
       
   526       }
       
   527   }
   477   /**
   528   /**
   478    * Render a block-toggling toolbar button.
   529    * Render a block-toggling toolbar button.
   479    *
   530    *
   480    * @param {String} type
   531    * @param {String} type
   481    * @param {String} icon
   532    * @param {String} icon
   482    * @return {Element}
   533    * @return {Element}
   483    */
   534    */
   484 
   535 
   485   renderBlockButton = (type, icon) => {
   536   renderBlockButton = (type, icon) => {
   486     const isActive = this.hasBlock(type)
   537     let isActive = this.hasBlock(type)
       
   538 
       
   539     if (['numbered-list', 'bulleted-list'].includes(type)) {
       
   540       const { value } = this.state
       
   541       const parent = value.document.getParent(value.blocks.first().key)
       
   542       isActive = this.hasBlock('list-item') && parent && parent.type === type
       
   543     }
   487     const onMouseDown = e => this.onClickBlock(e, type)
   544     const onMouseDown = e => this.onClickBlock(e, type)
   488 
   545 
   489     return (
   546     return (
   490       <span className="button" onMouseDown={onMouseDown} data-active={isActive}>
   547       <span className="button text-primary" onMouseDown={onMouseDown} data-active={isActive}>
   491         <span className="material-icons">{icon}</span>
   548         <span className="material-icons">{icon}</span>
   492       </span>
   549       </span>
   493     )
   550     )
   494   }
   551   }
   495 
   552 
       
   553   renderNode = props => {
       
   554     const { attributes, children, node } = props
       
   555 
       
   556     switch (node.type) {
       
   557       case 'block-quote':
       
   558         return <blockquote {...attributes}>{children}</blockquote>
       
   559       case 'bulleted-list':
       
   560         return <ul {...attributes}>{children}</ul>
       
   561       case 'heading-one':
       
   562         return <h1 {...attributes}>{children}</h1>
       
   563       case 'heading-two':
       
   564         return <h2 {...attributes}>{children}</h2>
       
   565       case 'list-item':
       
   566         return <li {...attributes}>{children}</li>
       
   567       case 'numbered-list':
       
   568         return <ol {...attributes}>{children}</ol>
       
   569     }
       
   570 }
       
   571 
   496   /**
   572   /**
   497    * Render the Slate editor.
   573    * Render the Slate editor.
   498    *
   574    *
   499    * @return {Element}
   575    * @return {Element}
   500    */
   576    */
   501 
   577 
   502   renderEditor = () => {
   578   renderEditor = () => {
   503     return (
   579     return (
       
   580       <div className="editor-wrapper sticky-bottom p-2">
   504       <div className="editor-slatejs">
   581       <div className="editor-slatejs">
   505         {this.renderHoveringMenu()}
   582         {this.renderHoveringMenu()}
   506         <Editor
   583         <Editor
   507           ref="editor"
   584           ref="editor"
   508           spellCheck
   585           spellCheck
   509           placeholder={'Enter some rich text...'}
   586           autoFocus
       
   587           placeholder={'Votre espace de prise de note...'}
   510           schema={schema}
   588           schema={schema}
   511           plugins={plugins}
   589           plugins={plugins}
   512           state={this.state.state}
   590           value={this.state.value}
   513           onChange={this.onChange}
   591           onChange={this.onChange}
   514           onKeyDown={this.onKeyDown}
   592           onKeyDown={this.onKeyDown}
       
   593           renderMark={this.renderMark}
       
   594           renderNode = {this.renderNode}
   515         />
   595         />
       
   596       </div>
   516       </div>
   597       </div>
   517     )
   598     )
   518   }
   599   }
   519 
   600 
   520   renderHoveringMenu = () => {
   601   renderHoveringMenu = () => {