client/src/components/SlateEditor/index.js
changeset 172 4b780ebbedc6
parent 171 03334a31130a
child 173 0e6703cd0968
equal deleted inserted replaced
171:03334a31130a 172:4b780ebbedc6
     1 import { Value } from 'slate';
     1 import { Value } from 'slate';
     2 import Plain from 'slate-plain-serializer';
     2 import Plain from 'slate-plain-serializer';
     3 import { Editor } from 'slate-react';
     3 import { Editor } from 'slate-react';
     4 import React from 'react';
     4 import React from 'react';
     5 import { Portal } from 'react-portal';
     5 import { PortalWithState } from 'react-portal';
     6 import { Trans, withNamespaces } from 'react-i18next';
     6 import { Trans, withNamespaces } from 'react-i18next';
     7 import * as R from 'ramda';
     7 import * as R from 'ramda';
     8 import HtmlSerializer from './HtmlSerializer';
     8 import HtmlSerializer from './HtmlSerializer';
     9 import AnnotationPlugin from './AnnotationPlugin';
     9 import AnnotationPlugin from './AnnotationPlugin';
    10 import CategoriesTooltip from './CategoriesTooltip';
    10 import CategoriesTooltip from './CategoriesTooltip';
    38     bold: {
    38     bold: {
    39       fontWeight: 'bold'
    39       fontWeight: 'bold'
    40     },
    40     },
    41     category: props => {
    41     category: props => {
    42       const data = props.mark.data;
    42       const data = props.mark.data;
    43       return <span style={{ backgroundColor: data.color }}>{props.children}</span>
    43       return <span style={{ backgroundColor: data.color }} {...props.attributes}>{props.children}</span>
    44     },
    44     },
    45     italic: {
    45     italic: {
    46       fontStyle: 'italic'
    46       fontStyle: 'italic'
    47     },
    47     },
    48     underlined: {
    48     underlined: {
    73   },
    73   },
    74 })
    74 })
    75 
    75 
    76 
    76 
    77 /**
    77 /**
    78  * The rich text example.
       
    79  *
    78  *
    80  * @type {Component}
    79  * @type {Component}
    81  */
    80  */
    82 
    81 
    83 class SlateEditor extends React.Component {
    82 class SlateEditor extends React.Component {
    98           currentSelectionEnd: end,
    97           currentSelectionEnd: end,
    99         });
    98         });
   100       }
    99       }
   101     });
   100     });
   102 
   101 
   103     plugins.push(annotationPlugin);
   102     // plugins.push(annotationPlugin);
   104 
   103 
   105 
   104 
   106     this.state = {
   105     this.state = {
   107       value: props.note ? Value.fromJSON(initialValue) : Plain.deserialize(''),
   106       value: props.note ? Value.fromJSON(initialValue) : Plain.deserialize(''),
   108       startedAt: null,
   107       startedAt: null,
   115       categories: [],
   114       categories: [],
   116       isCheckboxChecked: false,
   115       isCheckboxChecked: false,
   117       enterKeyValue: 0,
   116       enterKeyValue: 0,
   118     };
   117     };
   119 
   118 
   120     this.editor = React.createRef();
   119     this.editorRef = React.createRef();
       
   120     this.hoveringMenuRef = React.createRef();
       
   121   }
       
   122 
       
   123   get editor() {
       
   124     if(this.editorRef) {
       
   125       return this.editorRef.current;
       
   126     }
       
   127     return null;
   121   }
   128   }
   122 
   129 
   123   componentDidMount = () => {
   130   componentDidMount = () => {
   124     this.updateMenu();
   131     this.updateMenu();
   125     this.focus();
   132     this.focus();
   127 
   134 
   128   componentDidUpdate = () => {
   135   componentDidUpdate = () => {
   129     this.updateMenu();
   136     this.updateMenu();
   130   }
   137   }
   131 
   138 
       
   139   getDocumentLength = (document) => {
       
   140     return document.getBlocks().reduce((l, b) => l + b.text.length, 0)
       
   141   }
       
   142 
   132    /**
   143    /**
   133    * On change, save the new state.
   144    * On change, save the new state.
   134    *
   145    *
   135    * @param {Change} change
   146    * @param {Change} change
   136    */
   147    */
   137 
   148 
   138   onChange = ({value}) => {
   149   onChange = (change) => {
       
   150 
       
   151     const operationTypes = (change && change.operations) ? change.operations.map((o) => o.type).toArray() : [];
       
   152     console.log("CHANGE", change, operationTypes);
       
   153     const { value } = change;
   139 
   154 
   140     let newState = {
   155     let newState = {
   141       value: value,
   156       value: value,
   142       startedAt: this.state.startedAt
   157       startedAt: this.state.startedAt
   143     };
   158     };
   144 
   159 
   145     const isEmpty = value.document.length === 0;
   160     const isEmpty = this.getDocumentLength(value.document) === 0;
   146 
   161 
   147     // Reset timers when the text is empty
   162     // Reset timers when the text is empty
   148     if (isEmpty) {
   163     if (isEmpty) {
   149       Object.assign(newState, {
   164       Object.assign(newState, {
   150         startedAt: null,
   165         startedAt: null,
   158     if (!isEmpty && this.state.startedAt === null) {
   173     if (!isEmpty && this.state.startedAt === null) {
   159       Object.assign(newState, { startedAt: now() });
   174       Object.assign(newState, { startedAt: now() });
   160     }
   175     }
   161 
   176 
   162     const oldState = R.clone(this.state);
   177     const oldState = R.clone(this.state);
   163     this.setState(newState)
   178 
   164 
   179     const categories = value.marks.reduce((acc, mark) => {
   165     if (typeof this.props.onChange === 'function') {
   180       if(mark.type === 'category') {
   166       this.props.onChange(R.clone(this.state), oldState, newState);
   181         acc.push({
   167     }
   182           key: mark.data.get('key'),
       
   183           name: mark.data.get('name'),
       
   184           color: mark.data.get('color'),
       
   185           text: mark.data.get('text'),
       
   186           selection: {
       
   187             start: mark.data.get('selection').start,
       
   188             end: mark.data.get('selection').end,
       
   189           },
       
   190           comment: mark.data.get('comment')
       
   191         })
       
   192       }
       
   193       return acc;
       
   194     },
       
   195     []);
       
   196 
       
   197     console.log("ON CHANGE categorie", categories);
       
   198 
       
   199     newState['categories'] = categories;
       
   200 
       
   201     this.setState(newState, () => {
       
   202       if (typeof this.props.onChange === 'function') {
       
   203         this.props.onChange(R.clone(this.state), oldState, newState);
       
   204       }
       
   205     })
       
   206 
   168   }
   207   }
   169 
   208 
   170   /**
   209   /**
   171    * Check if the current selection has a mark with `type` in it.
   210    * Check if the current selection has a mark with `type` in it.
   172    *
   211    *
   173    * @param {String} type
   212    * @param {String} type
   174    * @return {Boolean}
   213    * @return {Boolean}
   175    */
   214    */
   176 
   215 
   177   hasMark = type => {
   216   hasMark = type => {
   178     const { value } = this.state
   217     const { value } = this.state;
   179     return value.activeMarks.some(mark => mark.type === type)
   218     return value.activeMarks.some(mark => mark.type === type);
   180   }
   219   }
   181 
   220 
   182   /**
   221   /**
   183    * Check if the any of the currently selected blocks are of `type`.
   222    * Check if the any of the currently selected blocks are of `type`.
   184    *
   223    *
   212     return categories.delete(categoryIndex)
   251     return categories.delete(categoryIndex)
   213   }
   252   }
   214 
   253 
   215   clear = () => {
   254   clear = () => {
   216     const value = Plain.deserialize('');
   255     const value = Plain.deserialize('');
   217     this.onChange({value});
   256     this.onChange({
       
   257       value,
       
   258     });
   218   }
   259   }
   219 
   260 
   220   focus = () => {
   261   focus = () => {
   221     if(this.editor.current) {
   262     if(this.editor) {
   222       this.editor.current.focus();
   263       this.editor.focus();
   223     }
   264     }
       
   265   }
       
   266 
       
   267   onClickCategoryButton = (openPortal, closePortal, isOpen, e) => {
       
   268     e.preventDefault();
       
   269     const { categories, value } = this.state
       
   270 
       
   271     let newCategories = categories.slice(0);
       
   272 
       
   273     // Can't use toggleMark here, because it expects the same object
       
   274     // @see https://github.com/ianstormtaylor/slate/issues/873
       
   275     if (this.hasMark('category')) {
       
   276       const categoryMarks = value.activeMarks.filter(mark => mark.type === 'category')
       
   277       categoryMarks.forEach(mark => {
       
   278         const key = mark.data.get('key');
       
   279         const text = mark.data.get('text');
       
   280 
       
   281         newCategories = R.reject(category => category.key === key && category.text === text, newCategories);
       
   282         this.editor.removeMark(mark)
       
   283       })
       
   284       this.setState({
       
   285         value: this.editor.value,
       
   286         categories: newCategories
       
   287       });
       
   288       closePortal();
       
   289     } else {
       
   290       openPortal();
       
   291     }
       
   292     // } else {
       
   293     //   isOpen ? closePortal() : openPortal();
       
   294     // }
   224   }
   295   }
   225 
   296 
   226   /**
   297   /**
   227    * When a mark button is clicked, toggle the current mark.
   298    * When a mark button is clicked, toggle the current mark.
   228    *
   299    *
   229    * @param {Event} e
   300    * @param {Event} e
   230    * @param {String} type
   301    * @param {String} type
   231    */
   302    */
   232 
   303 
   233   onClickMark = (e, type) => {
   304   onClickMark = (e, type) => {
   234 
   305     this.editor.toggleMark(type)
   235     e.preventDefault()
       
   236     const { value } = this.state
       
   237     let { categories } = this.state
       
   238 
       
   239     let isPortalOpen = false;
       
   240 
       
   241     if (type === 'category') {
       
   242       // Can't use toggleMark here, because it expects the same object
       
   243       // @see https://github.com/ianstormtaylor/slate/issues/873
       
   244       if (this.hasMark('category')) {
       
   245         const categoryMarks = value.activeMarks.filter(mark => mark.type === 'category')
       
   246         categoryMarks.forEach(mark => {
       
   247           const key = mark.data.key;
       
   248           const text = mark.data.text;
       
   249 
       
   250           categories = this.removeCategory(categories, key, text)
       
   251           const change = value.change().removeMark(mark)
       
   252           this.onChange(change)
       
   253         })
       
   254 
       
   255       } else {
       
   256         isPortalOpen = !this.state.isPortalOpen;
       
   257       }
       
   258     } else {
       
   259       const change = value.change().toggleMark(type)
       
   260       this.onChange(change)
       
   261     }
       
   262 
       
   263     this.setState({
       
   264       state: value.change,
       
   265       isPortalOpen: isPortalOpen,
       
   266       categories: categories
       
   267     })
       
   268   }
   306   }
   269 
   307 
   270   /**
   308   /**
   271    * When a block button is clicked, toggle the block type.
   309    * When a block button is clicked, toggle the block type.
   272    *
   310    *
   274    * @param {String} type
   312    * @param {String} type
   275    */
   313    */
   276 
   314 
   277   onClickBlock = (e, type) => {
   315   onClickBlock = (e, type) => {
   278     e.preventDefault()
   316     e.preventDefault()
   279     const { value } = this.state
   317 
   280     const change = value.change()
   318     const { editor } = this;
   281     const { document } = value
   319     const { value } = editor;
       
   320     const { document } = value;
   282 
   321 
   283     // Handle everything but list buttons.
   322     // Handle everything but list buttons.
   284     if (type !== 'bulleted-list' && type !== 'numbered-list') {
   323     if (type !== 'bulleted-list' && type !== 'numbered-list') {
   285       const isActive = this.hasBlock(type)
   324       const isActive = this.hasBlock(type)
   286       const isList = this.hasBlock('list-item')
   325       const isList = this.hasBlock('list-item')
   287 
   326 
   288       if (isList) {
   327       if (isList) {
   289         change
   328         editor
   290           .setBlocks(isActive ? DEFAULT_NODE : type)
   329           .setBlocks(isActive ? DEFAULT_NODE : type)
   291           .unwrapBlock('bulleted-list')
   330           .unwrapBlock('bulleted-list')
   292           .unwrapBlock('numbered-list')
   331           .unwrapBlock('numbered-list')
   293       }
   332       }
   294 
   333 
   295       else {
   334       else {
   296        change
   335        editor
   297           .setBlocks(isActive ? DEFAULT_NODE : type)
   336           .setBlocks(isActive ? DEFAULT_NODE : type)
   298       }
   337       }
   299     }
   338     }
   300 
   339 
   301     // Handle the extra wrapping required for list buttons.
   340     // Handle the extra wrapping required for list buttons.
   304       const isType = value.blocks.some((block) => {
   343       const isType = value.blocks.some((block) => {
   305         return !!document.getClosest(block.key, parent => parent.type === type)
   344         return !!document.getClosest(block.key, parent => parent.type === type)
   306       })
   345       })
   307 
   346 
   308       if (isList && isType) {
   347       if (isList && isType) {
   309        change
   348        editor
   310           .setBlocks(DEFAULT_NODE)
   349           .setBlocks(DEFAULT_NODE)
   311           .unwrapBlock('bulleted-list')
   350           .unwrapBlock('bulleted-list')
   312           .unwrapBlock('numbered-list')
   351           .unwrapBlock('numbered-list')
   313 
   352 
   314       } else if (isList) {
   353       } else if (isList) {
   315         change
   354         editor
   316           .unwrapBlock(type === 'bulleted-list' ? 'numbered-list' : 'bulleted-list')
   355           .unwrapBlock(type === 'bulleted-list' ? 'numbered-list' : 'bulleted-list')
   317           .wrapBlock(type)
   356           .wrapBlock(type)
   318 
   357 
   319       } else {
   358       } else {
   320         change
   359         editor
   321           .setBlocks('list-item')
   360           .setBlocks('list-item')
   322           .wrapBlock(type)
   361           .wrapBlock(type)
   323 
   362 
   324       }
   363       }
   325     }
   364     }
   326 
   365     // this.onChange(change)
   327 
   366   }
   328     this.onChange(change)
   367 
   329   }
   368   onPortalOpen = () => {
   330 
   369     console.log("onPORTAL OPEN", this);
   331   onPortalOpen = (portal) => {
   370     this.updateMenu();
   332     // When the portal opens, cache the menu element.
   371     // When the portal opens, cache the menu element.
   333     this.setState({ hoveringMenu: portal.firstChild })
   372     // this.setState({ hoveringMenu: this.portal.firstChild })
   334   }
   373   }
   335 
   374 
   336   onPortalClose = (portal) => {
   375   onPortalClose = (portal) => {
   337     let { value } = this.state
   376     console.log("onPORTAL CLOSE", this);
   338 
   377     // let { value } = this.state
   339     this.setState({
   378 
   340       value: value.change,
   379     // this.setState({
   341       isPortalOpen: false
   380     //   value: value.change,
   342     })
   381     //   isPortalOpen: false
   343   }
   382     // })
   344 
   383   }
   345   onCategoryClick = (category) => {
   384 
   346 
   385   getSelectionParams = () => {
   347     const { value, currentSelectionText, currentSelectionStart, currentSelectionEnd } = this.state;
   386 
   348     const change = value.change()
   387     const { value } = this.editor
       
   388     const { selection } = value
       
   389     const { start, end} = selection
       
   390 
       
   391     if (selection.isCollapsed) {
       
   392       return {};
       
   393     }
       
   394 
       
   395     const nodes = [];
       
   396     let hasStarted = false;
       
   397     let hasEnded = false;
       
   398 
       
   399     // Keep only the relevant nodes,
       
   400     // i.e. nodes which are contained within selection
       
   401     value.document.nodes.forEach((node) => {
       
   402       if (start.isInNode(node)) {
       
   403         hasStarted = true;
       
   404       }
       
   405       if (hasStarted && !hasEnded) {
       
   406         nodes.push(node);
       
   407       }
       
   408       if (end.isAtEndOfNode(node)) {
       
   409         hasEnded = true;
       
   410       }
       
   411     });
       
   412 
       
   413     // Concatenate the nodes text
       
   414     const text = nodes.map((node) => {
       
   415       let textStart = start.isInNode(node) ? start.offset : 0;
       
   416       let textEnd = end.isInNode(node) ? end.offset : node.text.length;
       
   417       return node.text.substring(textStart,textEnd);
       
   418     }).join('\n');
       
   419 
       
   420     return {
       
   421       currentSelectionText: text,
       
   422       currentSelectionStart: start.offset,
       
   423       currentSelectionEnd: end.offset
       
   424     };
       
   425   }
       
   426 
       
   427   onCategoryClick = (closePortal, category) => {
       
   428 
       
   429     console.log("ON CATEGORY CLICK");
       
   430     const { value } = this.state;
   349     let { categories } = this.state;
   431     let { categories } = this.state;
   350 
   432 
       
   433     const { currentSelectionText, currentSelectionStart, currentSelectionEnd } = this.getSelectionParams();
       
   434 
       
   435     if(!currentSelectionText) {
       
   436       closePortal();
       
   437       return;
       
   438     }
       
   439     console.log("ACTIVE MARKS", category, currentSelectionText, currentSelectionStart, currentSelectionEnd)
       
   440 
   351     const categoryMarks = value.activeMarks.filter(mark => mark.type === 'category')
   441     const categoryMarks = value.activeMarks.filter(mark => mark.type === 'category')
   352     categoryMarks.forEach(mark => change.removeMark(mark));
   442     categoryMarks.forEach(mark => this.editor.removeMark(mark));
   353 
   443 
   354     change.addMark({
   444     this.editor.addMark({
   355       type: 'category',
   445       type: 'category',
   356       data: {
   446       data: {
   357         text: currentSelectionText,
   447         text: currentSelectionText,
   358         selection: {
   448         selection: {
   359           start: currentSelectionStart,
   449           start: currentSelectionStart,
   360           end: currentSelectionEnd,
   450           end: currentSelectionEnd,
   361         },
   451         },
   362         color: category.color,
   452         color: category.color,
   363         key: category.key
   453         key: category.key,
       
   454         name: category.name,
       
   455         comment: category.comment
   364       }
   456       }
   365     })
   457     })
   366 
   458 
   367     Object.assign(category, {
   459     Object.assign(category, {
   368       text: currentSelectionText,
   460       text: currentSelectionText,
   369       selection: {
   461       selection: {
   370         start: currentSelectionStart,
   462         start: currentSelectionStart,
   371         end: currentSelectionEnd,
   463         end: currentSelectionEnd,
   372       },
   464       },
   373     });
   465     });
   374     categories = categories.push(category);
   466     categories.push(category);
   375 
   467 
   376     this.onChange(change)
   468     console.log("CATEGORIES", categories)
   377 
   469 
   378     this.setState({
   470     this.setState({
   379       value: value,
   471       categories: categories,
   380       isPortalOpen: false,
   472       value: this.editor.value
   381       categories: categories
   473     }, closePortal);
   382     });
       
   383   }
   474   }
   384 
   475 
   385   onButtonClick = () => {
   476   onButtonClick = () => {
   386     if (typeof this.props.onButtonClick === 'function') {
   477     if (typeof this.props.onButtonClick === 'function') {
   387       this.props.onButtonClick();
   478       this.props.onButtonClick();
   506     return (
   597     return (
   507       <div className="menu toolbar-menu d-flex sticky-top bg-secondary">
   598       <div className="menu toolbar-menu d-flex sticky-top bg-secondary">
   508           {this.renderMarkButton('bold', 'format_bold')}
   599           {this.renderMarkButton('bold', 'format_bold')}
   509           {this.renderMarkButton('italic', 'format_italic')}
   600           {this.renderMarkButton('italic', 'format_italic')}
   510           {this.renderMarkButton('underlined', 'format_underlined')}
   601           {this.renderMarkButton('underlined', 'format_underlined')}
   511           {this.renderMarkButton('category', 'label')}
   602           {this.renderCategoryButton()}
   512 
       
   513 
   603 
   514           {this.renderBlockButton('numbered-list', 'format_list_numbered')}
   604           {this.renderBlockButton('numbered-list', 'format_list_numbered')}
   515           {this.renderBlockButton('bulleted-list', 'format_list_bulleted')}
   605           {this.renderBlockButton('bulleted-list', 'format_list_bulleted')}
   516 
   606 
   517           {this.renderToolbarButtons()}
   607           {this.renderToolbarButtons()}
   561         <span className="material-icons">{icon}</span>
   651         <span className="material-icons">{icon}</span>
   562       </span>
   652       </span>
   563     )
   653     )
   564   }
   654   }
   565 
   655 
   566     // Add a `renderMark` method to render marks.
   656     /**
   567 
   657    * Render a mark-toggling toolbar button.
   568     renderMark = props => {
       
   569       const { children, mark, attributes } = props
       
   570 
       
   571       switch (mark.type) {
       
   572         case 'bold':
       
   573           return <strong {...attributes}>{children}</strong>
       
   574         case 'code':
       
   575           return <code {...attributes}>{children}</code>
       
   576         case 'italic':
       
   577           return <em {...attributes}>{children}</em>
       
   578         case 'underlined':
       
   579           return <ins {...attributes}>{children}</ins>
       
   580         default:
       
   581           return {children};
       
   582       }
       
   583   }
       
   584   /**
       
   585    * Render a block-toggling toolbar button.
       
   586    *
   658    *
   587    * @param {String} type
   659    * @param {String} type
   588    * @param {String} icon
   660    * @param {String} icon
   589    * @return {Element}
   661    * @return {Element}
   590    */
   662    */
   591 
   663 
       
   664   renderCategoryButton = () => {
       
   665     const isActive = this.hasMark('category');
       
   666     //const onMouseDown = e => this.onClickMark(e, type)
       
   667     const markActivation = "button sticky-top" + ((!isActive)?" text-primary":" text-dark");
       
   668 
       
   669     return (
       
   670       <PortalWithState
       
   671         // closeOnOutsideClick
       
   672         closeOnEsc
       
   673         onOpen={this.onPortalOpen}
       
   674         onClose={this.onPortalClose}
       
   675       >
       
   676         {({ openPortal, closePortal, isOpen, portal }) => {
       
   677           console.log("PORTAL", isOpen);
       
   678           const onMouseDown = R.partial(this.onClickCategoryButton, [openPortal, closePortal, isOpen]);
       
   679           const onCategoryClick = R.partial(this.onCategoryClick, [closePortal,]);
       
   680           return (
       
   681             <React.Fragment>
       
   682               <span className={markActivation} onMouseDown={onMouseDown} data-active={isActive}>
       
   683                 <span className="material-icons">label</span>
       
   684               </span>
       
   685               {portal(
       
   686                 <div className="hovering-menu" ref={this.hoveringMenuRef}>
       
   687                   <CategoriesTooltip categories={this.props.annotationCategories || defaultAnnotationsCategories} onCategoryClick={onCategoryClick} />
       
   688                 </div>
       
   689               )}
       
   690             </React.Fragment>
       
   691           )}
       
   692         }
       
   693       </PortalWithState>
       
   694     )
       
   695   }
       
   696 
       
   697   // Add a `renderMark` method to render marks.
       
   698 
       
   699   renderMark = (props, editor, next) => {
       
   700     const { children, mark, attributes } = props
       
   701 
       
   702     console.log("renderMark", mark, mark.type, mark.data.color);
       
   703     switch (mark.type) {
       
   704       case 'bold':
       
   705         return <strong {...attributes}>{children}</strong>
       
   706       case 'code':
       
   707         return <code {...attributes}>{children}</code>
       
   708       case 'italic':
       
   709         return <em {...attributes}>{children}</em>
       
   710       case 'underlined':
       
   711         return <ins {...attributes}>{children}</ins>
       
   712       case 'category':
       
   713         let spanStyle = {
       
   714           backgroundColor: mark.data.get('color')
       
   715         };
       
   716         return <span {...attributes} style={ spanStyle } >{children}</span>
       
   717       default:
       
   718         return next();
       
   719     }
       
   720   }
       
   721   /**
       
   722    * Render a block-toggling toolbar button.
       
   723    *
       
   724    * @param {String} type
       
   725    * @param {String} icon
       
   726    * @return {Element}
       
   727    */
       
   728 
   592   renderBlockButton = (type, icon) => {
   729   renderBlockButton = (type, icon) => {
   593     let isActive = this.hasBlock(type)
   730     let isActive = this.hasBlock(type)
   594 
   731 
   595     if (['numbered-list', 'bulleted-list'].includes(type)) {
   732     if (['numbered-list', 'bulleted-list'].includes(type)) {
   596       const { value } = this.state
   733       const { value } = this.state;
   597       const parent = value.document.getParent(value.blocks.first().key)
   734       const firstBlock = value.blocks.first();
   598       isActive = this.hasBlock('list-item') && parent && parent.type === type
   735       if(firstBlock) {
       
   736         const parent = value.document.getParent(firstBlock.key);
       
   737         isActive = this.hasBlock('list-item') && parent && parent.type === type;
       
   738       }
   599     }
   739     }
   600     const onMouseDown = e => this.onClickBlock(e, type)
   740     const onMouseDown = e => this.onClickBlock(e, type)
   601     const blockActivation = "button sticky-top" + ((!isActive)?" text-primary":" text-dark");
   741     const blockActivation = "button sticky-top" + ((!isActive)?" text-primary":" text-dark");
   602 
   742 
   603     return (
   743     return (
   605         <span className="material-icons">{icon}</span>
   745         <span className="material-icons">{icon}</span>
   606       </span>
   746       </span>
   607     )
   747     )
   608   }
   748   }
   609 
   749 
   610   renderNode = props => {
   750   renderNode = (props, editor, next) => {
   611     const { attributes, children, node } = props
   751     const { attributes, children, node } = props
   612 
   752 
   613     switch (node.type) {
   753     switch (node.type) {
   614       case 'block-quote':
   754       case 'block-quote':
   615         return <blockquote {...attributes}>{children}</blockquote>
   755         return <blockquote {...attributes}>{children}</blockquote>
   622       case 'list-item':
   762       case 'list-item':
   623         return <li {...attributes}>{children}</li>
   763         return <li {...attributes}>{children}</li>
   624       case 'numbered-list':
   764       case 'numbered-list':
   625         return <ol {...attributes}>{children}</ol>
   765         return <ol {...attributes}>{children}</ol>
   626       default:
   766       default:
   627         return null;
   767         return next();
   628     }
   768     }
   629 }
   769 }
   630 
   770 
   631   /**
   771   /**
   632    * Render the Slate editor.
   772    * Render the Slate editor.
   636 
   776 
   637   renderEditor = () => {
   777   renderEditor = () => {
   638     const t = this.props.t;
   778     const t = this.props.t;
   639     return (
   779     return (
   640       <div className="editor-slatejs p-2">
   780       <div className="editor-slatejs p-2">
   641         {this.renderHoveringMenu()}
   781         {/* {this.renderHoveringMenu()} */}
   642         <Editor
   782         <Editor
   643           ref={this.editor}
   783           ref={this.editorRef}
   644           spellCheck
   784           spellCheck
   645           placeholder={t('slate_editor.placeholder')}
   785           placeholder={t('slate_editor.placeholder')}
   646           schema={schema}
   786           // schema={schema}
   647           plugins={plugins}
   787           plugins={plugins}
   648           value={this.state.value}
   788           value={this.state.value}
   649           onChange={this.onChange}
   789           onChange={this.onChange}
   650           onKeyDown={this.onKeyDown}
   790           // onKeyDown={this.onKeyDown}
   651           renderMark={this.renderMark}
   791           renderMark={this.renderMark}
   652           renderNode = {this.renderNode}
   792           renderNode = {this.renderNode}
   653         />
   793         />
   654       </div>
   794       </div>
   655     )
   795     )
   656   }
   796   }
   657 
   797 
   658   renderHoveringMenu = () => {
   798   // renderHoveringMenu = () => {
   659     return (
   799   //   return (
   660       <Portal ref="portal"
   800   //     <Portal ref="portal"
   661         isOpened={this.state.isPortalOpen} isOpen={this.state.isPortalOpen}
   801   //       isOpened={this.state.isPortalOpen} isOpen={this.state.isPortalOpen}
   662         onOpen={this.onPortalOpen}
   802   //       onOpen={this.onPortalOpen}
   663         onClose={this.onPortalClose}
   803   //       onClose={this.onPortalClose}
   664         closeOnOutsideClick={false} closeOnEsc={true}>
   804   //       closeOnOutsideClick={false} closeOnEsc={true}>
   665         <div className="hovering-menu">
   805   //       <div className="hovering-menu">
   666           <CategoriesTooltip categories={this.props.annotationCategories || defaultAnnotationsCategories} onCategoryClick={this.onCategoryClick} />
   806   //         <CategoriesTooltip categories={this.props.annotationCategories || defaultAnnotationsCategories} onCategoryClick={this.onCategoryClick} />
   667         </div>
   807   //       </div>
   668       </Portal>
   808   //     </Portal>
   669     )
   809   //   )
   670   }
   810   // }
   671 
   811 
   672   updateMenu = () => {
   812   updateMenu = () => {
   673 
   813 
   674     const { hoveringMenu } = this.state
   814     // const { hoveringMenu } = this.state
       
   815     const hoveringMenu = this.hoveringMenuRef.current;
   675 
   816 
   676     if (!hoveringMenu) return
   817     if (!hoveringMenu) return
   677 
   818 
   678     // if (state.isBlurred || state.isCollapsed) {
   819     // if (state.isBlurred || state.isCollapsed) {
   679     //   hoveringMenu.removeAttribute('style')
   820     //   hoveringMenu.removeAttribute('style')
   688 
   829 
   689     const range = selection.getRangeAt(0)
   830     const range = selection.getRangeAt(0)
   690     const rect = range.getBoundingClientRect()
   831     const rect = range.getBoundingClientRect()
   691 
   832 
   692     hoveringMenu.style.opacity = 1
   833     hoveringMenu.style.opacity = 1
   693     hoveringMenu.style.top = `${rect.top + window.scrollY + hoveringMenu.offsetHeight}px`
   834     hoveringMenu.style.top = `${rect.top + rect.height + window.scrollY + hoveringMenu.offsetHeight}px`
   694     hoveringMenu.style.left = `${rect.left + window.scrollX - hoveringMenu.offsetWidth / 2 + rect.width / 2}px`
   835     hoveringMenu.style.left = `${rect.left + window.scrollX - hoveringMenu.offsetWidth / 2 + rect.width / 2}px`
   695   }
   836   }
   696 
   837 
   697 }
   838 }
   698 
   839