Introduce "annotation" plugin.
- Wrap mark around text.
- Store selected text in mark data.
- Colorize selected text.
--- /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;
--- 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;
+
+}
--- 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 <pre><code>{children}</code></pre>
case 'paragraph':
case 'line': return <p>{children}</p>
- case 'quote': return <blockquote>{children}</blockquote>
+ 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 <strong>{children}</strong>
case 'italic': return <em>{children}</em>
case 'underline': return <u>{children}</u>
+ case 'annotation': return <span style={annotationStyle}>{children}</span>
+ default: return;
}
}
}
--- 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 (
<div>
{notes.map((note) =>
- <Note note={note} />
+ <Note note={note} key={note.id} />
)}
</div>
);
--- 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 => <blockquote {...props.attributes}>{props.children}</blockquote>,
'bulleted-list': props => <ul {...props.attributes}>{props.children}</ul>,
- 'heading-one': props => <h1 {...props.attributes}>{props.children}</h1>,
- 'heading-two': props => <h2 {...props.attributes}>{props.children}</h2>,
'list-item': props => <li {...props.attributes}>{props.children}</li>,
'numbered-list': props => <ol {...props.attributes}>{props.children}</ol>,
},
@@ -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')}
</div>
@@ -356,6 +369,7 @@
spellCheck
placeholder={'Enter some rich text...'}
schema={schema}
+ plugins={plugins}
state={this.state.state}
onChange={this.onChange}
onKeyDown={this.onKeyDown}