diff -r a8300ef1876e -r 284e866f55c7 client/src/components/SlateEditor.js --- a/client/src/components/SlateEditor.js Wed Jun 07 18:18:44 2017 +0200 +++ b/client/src/components/SlateEditor.js Thu Jun 08 11:13:41 2017 +0200 @@ -1,8 +1,10 @@ import { Editor, Plain, Raw } from 'slate' import React from 'react' -import moment from 'moment'; +import Portal from 'react-portal' +import moment from 'moment' import HtmlSerializer from '../HtmlSerializer' -import AnnotationPlugin from '../AnnotationPlugin'; +import AnnotationPlugin from '../AnnotationPlugin' +import CategoriesTooltip from './CategoriesTooltip' const plugins = []; @@ -17,7 +19,8 @@ * * @type {Object} */ - +// TODO Check if we can move this to the plugin using the schema option +// https://docs.slatejs.org/reference/plugins/plugin.html#schema const schema = { nodes: { 'bulleted-list': props => , @@ -28,12 +31,16 @@ bold: { fontWeight: 'bold' }, - // TODO Check if we can move this to the plugin using the schema option - // https://docs.slatejs.org/reference/plugins/plugin.html#schema - annotation: { + // This is a "temporary" mark added when the hovering menu is open + highlight: { textDecoration: 'underline', textDecorationStyle: 'dotted', - backgroundColor: 'yellow', + backgroundColor: '#ccc', + }, + // This is the mark actually used for annotations + annotation: props => { + const data = props.mark.data; + return {props.children} }, italic: { fontStyle: 'italic' @@ -44,6 +51,12 @@ } } +const annotationCategories = [ + { key: 'important', name: 'Important', color: '#F1C40F' }, + { key: 'keyword', name: 'Mot-clé', color: '#2ECC71' }, + { key: 'comment', name: 'Commentaire', color: '#3498DB' } +]; + /** * The rich text example. * @@ -72,14 +85,21 @@ state: Plain.deserialize(''), startedAt: null, finishedAt: null, - currentSelectionText: '' + currentSelectionText: '', + hoveringMenu: null, + isPortalOpen: false }; } - componentDidMount() { + componentDidMount = () => { + this.updateMenu(); this.focus(); } + componentDidUpdate = () => { + this.updateMenu(); + } + /** * Check if the current selection has a mark with `type` in it. * @@ -207,11 +227,14 @@ onClickMark = (e, type) => { e.preventDefault() - let { state } = this.state + let { state, hoveringMenu } = this.state let toggleMarkOptions; - if (type === 'annotation') { + let isPortalOpen = false; + + if (type === 'highlight') { toggleMarkOptions = { type: type, data: { text: this.state.currentSelectionText } } + isPortalOpen = !this.state.isPortalOpen; } else { toggleMarkOptions = type; } @@ -221,7 +244,10 @@ .toggleMark(toggleMarkOptions) .apply() - this.setState({ state }) + this.setState({ + state: state, + isPortalOpen: isPortalOpen + }) } /** @@ -282,6 +308,51 @@ this.setState({ state }) } + onPortalOpen = (portal) => { + // When the portal opens, cache the menu element. + this.setState({ hoveringMenu: portal.firstChild }) + } + + onPortalClose = (portal) => { + + let { state } = this.state + const transform = state.transform(); + + state.marks.forEach(mark => { + if (mark.type === 'highlight') { + transform.removeMark(mark) + } + }); + + this.setState({ + state: transform.apply(), + isPortalOpen: false + }) + } + + onCategoryClick = (category) => { + + const { state } = this.state; + const transform = state.transform(); + + state.marks.forEach(mark => transform.removeMark(mark)); + + transform.addMark({ + type: 'annotation', + data: { + text: this.state.currentSelectionText, + color: category.color, + key: category.key + } + }) + + this.setState({ + state: transform.apply(), + isPortalOpen: false + }); + + } + /** * Render. * @@ -309,7 +380,7 @@ {this.renderMarkButton('bold', 'format_bold')} {this.renderMarkButton('italic', 'format_italic')} {this.renderMarkButton('underlined', 'format_underlined')} - {this.renderMarkButton('annotation', 'label')} + {this.renderMarkButton('highlight', 'label')} {this.renderBlockButton('numbered-list', 'format_list_numbered')} {this.renderBlockButton('bulleted-list', 'format_list_bulleted')} @@ -364,6 +435,7 @@ renderEditor = () => { return (
+ {this.renderHoveringMenu()} { + return ( + +
+ +
+
+ ) + } + + updateMenu = () => { + + const { hoveringMenu, state } = this.state + + if (!hoveringMenu) return + + // if (state.isBlurred || state.isCollapsed) { + // hoveringMenu.removeAttribute('style') + // return + // } + + const selection = window.getSelection() + + if (selection.isCollapsed) { + return + } + + const range = selection.getRangeAt(0) + const rect = range.getBoundingClientRect() + + hoveringMenu.style.opacity = 1 + hoveringMenu.style.top = `${rect.top + window.scrollY + hoveringMenu.offsetHeight}px` + hoveringMenu.style.left = `${rect.left + window.scrollX - hoveringMenu.offsetWidth / 2 + rect.width / 2}px` + } + } /**