# HG changeset patch # User Alexandre Segura # Date 1496757401 -7200 # Node ID f1b125b95fe90900fb0a7dfb279910357fddc005 # Parent dab2a16500e0d8c861de79cfa8bfd57651757289 Introduce "annotation" plugin. - Wrap mark around text. - Store selected text in mark data. - Colorize selected text. diff -r dab2a16500e0 -r f1b125b95fe9 client/src/AnnotationPlugin.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/AnnotationPlugin.js Tue Jun 06 15:56:41 2017 +0200 @@ -0,0 +1,57 @@ +function AnnotationPlugin(options) { + + const { onChange } = options + + return { + onSelect(event, data, state, editor) { + event.preventDefault() + + const selection = data.selection; + + const startOffset = selection.startOffset; + const endOffset = selection.endOffset; + + if (selection.isCollapsed) { + return; + } + + let nodes = []; + let hasStarted = false; + let hasEnded = false; + + // Keep only the relevant nodes, + // i.e. nodes which are contained within selection + state.document.nodes.forEach((node) => { + if (selection.hasStartIn(node)) { + hasStarted = true; + } + if (hasStarted && !hasEnded) { + nodes.push(node); + } + if (selection.hasEndIn(node)) { + hasEnded = true; + } + }); + + let text = ''; + + // Concatenate the nodes text + if (nodes.length === 1) { + text = nodes[0].text.substring(startOffset, endOffset); + } else { + text = nodes.map((node) => { + if (selection.hasStartIn(node)) return node.text.substring(startOffset); + if (selection.hasEndIn(node)) return node.text.substring(0, endOffset); + return node.text; + }).join('\n'); + } + + if (onChange) { + onChange(text); + } + } + + }; +} + +export default AnnotationPlugin; diff -r dab2a16500e0 -r f1b125b95fe9 client/src/App.scss --- a/client/src/App.scss Thu Jun 01 19:01:03 2017 +0200 +++ b/client/src/App.scss Tue Jun 06 15:56:41 2017 +0200 @@ -1,3 +1,5 @@ +@import "bootstrap/variables"; + .App { text-align: center; } @@ -19,13 +21,13 @@ } .toolbar-menu { - padding: 1px 0 17px 18px; - // margin: 0 -20px; + padding-bottom: 10px; border-bottom: 2px solid #eee; margin-bottom: 20px; .button { color: #ccc; cursor: pointer; + margin-right: 10px; } .button[data-active="true"] { @@ -48,6 +50,7 @@ position: relative; padding-left: 70px; margin-bottom: 20px; + min-height: ($line-height-computed * 3); &:before { content: ""; @@ -75,3 +78,10 @@ left: 0; } } + +span.annotation { + background-color: yellow; + text-decoration-line: underline; + text-decoration-style: dotted; + +} diff -r dab2a16500e0 -r f1b125b95fe9 client/src/HtmlSerializer.js --- a/client/src/HtmlSerializer.js Thu Jun 01 19:01:03 2017 +0200 +++ b/client/src/HtmlSerializer.js Tue Jun 06 15:56:41 2017 +0200 @@ -2,9 +2,7 @@ import { Html } from 'slate' const BLOCK_TAGS = { - blockquote: 'quote', p: 'paragraph', - pre: 'code' } // Add a dictionary of mark tags. @@ -12,6 +10,13 @@ em: 'italic', strong: 'bold', u: 'underline', + annotation: 'span' +} + +const annotationStyle = { + textDecoration: 'underline', + textDecorationStyle: 'dotted', + backgroundColor: 'yellow' } const rules = [ @@ -27,12 +32,11 @@ } }, serialize(object, children) { - if (object.kind != 'block') return + if (object.kind !== 'block') return switch (object.type) { - case 'code': return
{children}
case 'paragraph': case 'line': return

{children}

- case 'quote': return
{children}
+ default: return; } } }, @@ -48,11 +52,13 @@ } }, serialize(object, children) { - if (object.kind != 'mark') return + if (object.kind !== 'mark') return switch (object.type) { case 'bold': return {children} case 'italic': return {children} case 'underline': return {children} + case 'annotation': return {children} + default: return; } } } diff -r dab2a16500e0 -r f1b125b95fe9 client/src/components/NotesList.js --- a/client/src/components/NotesList.js Thu Jun 01 19:01:03 2017 +0200 +++ b/client/src/components/NotesList.js Tue Jun 06 15:56:41 2017 +0200 @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import Immutable from 'immutable'; -import { ListGroup, ListGroupItem, Alert } from 'react-bootstrap'; +import { Alert } from 'react-bootstrap'; import Note from './Note'; @@ -18,7 +18,7 @@ return (
{notes.map((note) => - + )}
); diff -r dab2a16500e0 -r f1b125b95fe9 client/src/components/SlateEditor.js --- a/client/src/components/SlateEditor.js Thu Jun 01 19:01:03 2017 +0200 +++ b/client/src/components/SlateEditor.js Tue Jun 06 15:56:41 2017 +0200 @@ -2,6 +2,9 @@ import React from 'react' import moment from 'moment'; import HtmlSerializer from '../HtmlSerializer' +import AnnotationPlugin from '../AnnotationPlugin'; + +const plugins = []; /** * Define the default node type. @@ -17,10 +20,7 @@ const schema = { nodes: { - 'block-quote': props =>
{props.children}
, 'bulleted-list': props => , - 'heading-one': props =>

{props.children}

, - 'heading-two': props =>

{props.children}

, 'list-item': props =>
  • {props.children}
  • , 'numbered-list': props =>
      {props.children}
    , }, @@ -28,11 +28,12 @@ bold: { fontWeight: 'bold' }, - code: { - fontFamily: 'monospace', - backgroundColor: '#eee', - padding: '3px', - borderRadius: '4px' + // TODO Check if we can move this to the plugin using the schema option + // https://docs.slatejs.org/reference/plugins/plugin.html#schema + annotation: { + textDecoration: 'underline', + textDecorationStyle: 'dotted', + backgroundColor: 'yellow', }, italic: { fontStyle: 'italic' @@ -58,10 +59,20 @@ */ constructor(props) { super(props); + + const annotationPlugin = AnnotationPlugin({ + onChange: (text) => { + this.setState({ currentSelectionText: text }); + } + }); + + plugins.push(annotationPlugin); + this.state = { state: Plain.deserialize(''), startedAt: null, - finishedAt: null + finishedAt: null, + currentSelectionText: '' }; } @@ -174,9 +185,6 @@ case 'u': mark = 'underlined' break - case '`': - mark = 'code' - break default: return } @@ -201,9 +209,16 @@ e.preventDefault() let { state } = this.state + let toggleMarkOptions; + if (type === 'annotation') { + toggleMarkOptions = { type: type, data: { text: this.state.currentSelectionText } } + } else { + toggleMarkOptions = type; + } + state = state .transform() - .toggleMark(type) + .toggleMark(toggleMarkOptions) .apply() this.setState({ state }) @@ -294,10 +309,8 @@ {this.renderMarkButton('bold', 'format_bold')} {this.renderMarkButton('italic', 'format_italic')} {this.renderMarkButton('underlined', 'format_underlined')} - {this.renderMarkButton('code', 'code')} - {this.renderBlockButton('heading-one', 'looks_one')} - {this.renderBlockButton('heading-two', 'looks_two')} - {this.renderBlockButton('block-quote', 'format_quote')} + {this.renderMarkButton('annotation', 'label')} + {this.renderBlockButton('numbered-list', 'format_list_numbered')} {this.renderBlockButton('bulleted-list', 'format_list_bulleted')} @@ -356,6 +369,7 @@ spellCheck placeholder={'Enter some rich text...'} schema={schema} + plugins={plugins} state={this.state.state} onChange={this.onChange} onKeyDown={this.onKeyDown}