diff -r 384f4539b76a -r 5c3af4f10e92 client/src/components/SlateEditor.js --- a/client/src/components/SlateEditor.js Wed Sep 05 13:48:10 2018 +0200 +++ b/client/src/components/SlateEditor.js Tue Sep 25 02:02:13 2018 +0200 @@ -1,4 +1,6 @@ -import { Editor, Plain, Raw } from 'slate' +import { Value } from 'slate' +import Plain from 'slate-plain-serializer' +import { Editor } from 'slate-react' import React from 'react' import Portal from 'react-portal' import Immutable from 'immutable' @@ -41,11 +43,32 @@ fontStyle: 'italic' }, underlined: { - textDecoration: 'underline' + textDecoration: 'underlined' } } } +const initialValue = Value.fromJSON({ + document: { + nodes: [ + { + object: 'block', + type: 'paragraph', + nodes: [ + { + object: 'text', + leaves: [ + { + text: '', + }, + ], + }, + ], + }, + ], + }, +}) + /** * The rich text example. * @@ -74,8 +97,9 @@ plugins.push(annotationPlugin); + this.state = { - state: props.note ? Raw.deserialize(props.note.raw) : Plain.deserialize(''), + value: props.note ? initialValue : Plain.deserialize(''), startedAt: null, finishedAt: null, currentSelectionText: '', @@ -97,44 +121,20 @@ this.updateMenu(); } - /** - * Check if the current selection has a mark with `type` in it. + /** + * On change, save the new state. * - * @param {String} type - * @return {Boolean} + * @param {Change} change */ - 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) => { + onChange = ({value}) => { let newState = { - state: state, + value: value, startedAt: this.state.startedAt }; - const isEmpty = state.document.length === 0; + const isEmpty = value.document.length === 0; // Reset timers when the text is empty if (isEmpty) { @@ -158,16 +158,40 @@ } } + /** + * Check if the current selection has a mark with `type` in it. + * + * @param {String} type + * @return {Boolean} + */ + + hasMark = type => { + const { value } = this.state + return value.activeMarks.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 { value } = this.state + return value.blocks.some(node => node.type === type) +} + asPlain = () => { - return Plain.serialize(this.state.state); + return Plain.serialize(this.state.value); } asRaw = () => { - return Raw.serialize(this.state.state); + return JSON.stringify(this.state.value.toJSON()); } asHtml = () => { - return HtmlSerializer.serialize(this.state.state); + return HtmlSerializer.serialize(this.state.value); } asCategories = () => { @@ -180,8 +204,8 @@ } clear = () => { - const state = Plain.deserialize(''); - this.onChange(state); + const value = Plain.deserialize(''); + this.onChange({value}); } focus = () => { @@ -193,46 +217,48 @@ * * @param {Event} e * @param {Object} data - * @param {State} state - * @return {State} + * @param {Change} change + * @return {Change} */ - onKeyDown = (e, data, state) => { + onKeyDown = (e, change) => { + // if (data.key === 'enter' && this.props.isChecked && typeof this.props.onEnterKeyDown === 'function') { + + // e.preventDefault(); + // this.props.onEnterKeyDown(); + + // return change; + // } + + // if (!data.isMod) return + if (!e.ctrlKey) return + // Decide what to do based on the key code... + switch (e.key) { + // When "B" is pressed, add a "bold" mark to the text. + case 'b': { + e.preventDefault() + change.toggleMark('bold') - if (data.key === 'enter' && this.props.isChecked && typeof this.props.onEnterKeyDown === 'function') { - e.preventDefault(); - this.props.onEnterKeyDown(); + return true + } + case 'i': { + // When "U" is pressed, add an "italic" mark to the text. + e.preventDefault() + change.toggleMark('italic') - return state; + return true + } + case 'u': { + // When "U" is pressed, add an "underline" mark to the text. + e.preventDefault() + change.toggleMark('underlined') + + return true + } + } } - if (!data.isMod) return - let mark - - switch (data.key) { - case 'b': - mark = 'bold' - break - case 'i': - mark = 'italic' - break - case 'u': - mark = 'underlined' - break - default: - return - } - - state = state - .transform() - .toggleMark(mark) - .apply() - - e.preventDefault() - return state - } - - /** + /** * When a mark button is clicked, toggle the current mark. * * @param {Event} e @@ -240,10 +266,10 @@ */ onClickMark = (e, type) => { - e.preventDefault() - const { state } = this.state + + e.preventDefault() + const { value } = this.state let { categories } = this.state - const transform = state.transform() let isPortalOpen = false; @@ -251,24 +277,26 @@ // Can't use toggleMark here, because it expects the same object // @see https://github.com/ianstormtaylor/slate/issues/873 if (this.hasMark('category')) { - const categoryMarks = state.marks.filter(mark => mark.type === 'category') + const categoryMarks = value.activeMarks.filter(mark => mark.type === 'category') categoryMarks.forEach(mark => { const key = mark.data.get('key'); const text = mark.data.get('text'); categories = this.removeCategory(categories, key, text) - transform.removeMark(mark) + const change = value.change().removeMark(mark) + this.onChange(change) }) } else { isPortalOpen = !this.state.isPortalOpen; } } else { - transform.toggleMark(type) + const change = value.change().toggleMark(type) + this.onChange(change) } this.setState({ - state: transform.apply(), + state: value.change, isPortalOpen: isPortalOpen, categories: categories }) @@ -283,9 +311,9 @@ onClickBlock = (e, type) => { e.preventDefault() - let { state } = this.state - const transform = state.transform() - const { document } = state + const { value } = this.state + const change = value.change() + const { document } = value // Handle everything but list buttons. if (type !== 'bulleted-list' && type !== 'numbered-list') { @@ -293,43 +321,46 @@ const isList = this.hasBlock('list-item') if (isList) { - transform - .setBlock(isActive ? DEFAULT_NODE : type) + change + .setBlocks(isActive ? DEFAULT_NODE : type) .unwrapBlock('bulleted-list') .unwrapBlock('numbered-list') } else { - transform - .setBlock(isActive ? DEFAULT_NODE : type) + change + .setBlocks(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) => { + const isType = value.blocks.some((block) => { return !!document.getClosest(block.key, parent => parent.type === type) }) if (isList && isType) { - transform - .setBlock(DEFAULT_NODE) + change + .setBlocks(DEFAULT_NODE) .unwrapBlock('bulleted-list') .unwrapBlock('numbered-list') + } else if (isList) { - transform + change .unwrapBlock(type === 'bulleted-list' ? 'numbered-list' : 'bulleted-list') .wrapBlock(type) + } else { - transform - .setBlock('list-item') + change + .setBlocks('list-item') .wrapBlock(type) + } } - state = transform.apply() - this.setState({ state }) + + this.onChange(change) } onPortalOpen = (portal) => { @@ -338,25 +369,24 @@ } onPortalClose = (portal) => { - let { state } = this.state - const transform = state.transform(); + let { value } = this.state this.setState({ - state: transform.apply(), + value: value.change, isPortalOpen: false }) } onCategoryClick = (category) => { - const { state, currentSelectionText, currentSelectionStart, currentSelectionEnd } = this.state; + const { value, currentSelectionText, currentSelectionStart, currentSelectionEnd } = this.state; + const change = value.change() let { categories } = this.state; - const transform = state.transform(); - const categoryMarks = state.marks.filter(mark => mark.type === 'category') - categoryMarks.forEach(mark => transform.removeMark(mark)); + const categoryMarks = value.activeMarks.filter(mark => mark.type === 'category') + categoryMarks.forEach(mark => change.removeMark(mark)); - transform.addMark({ + change.addMark({ type: 'category', data: { text: currentSelectionText, @@ -368,6 +398,7 @@ key: category.key } }) + this.onChange(change) Object.assign(category, { text: currentSelectionText, @@ -379,7 +410,7 @@ categories = categories.push(category); this.setState({ - state: transform.apply(), + value: value, isPortalOpen: false, categories: categories }); @@ -405,10 +436,12 @@ render = () => { return ( -