Custom block components + soft break handling
This commit is contained in:
parent
fb38de60d3
commit
b1a56f60cd
@ -1,9 +1,10 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { Editor } from 'slate';
|
import { Editor, Plain } from 'slate';
|
||||||
import Markdown from 'slate-markdown-serializer';
|
import Markdown from 'slate-markdown-serializer';
|
||||||
|
import Block from './MarkdownControlElements/Block';
|
||||||
|
import { Icon } from '../UI';
|
||||||
|
|
||||||
const markdown = new Markdown();
|
const markdown = new Markdown();
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Slate Render Configuration
|
* Slate Render Configuration
|
||||||
*/
|
*/
|
||||||
@ -13,16 +14,20 @@ const DEFAULT_NODE = 'paragraph';
|
|||||||
|
|
||||||
// Local node renderers.
|
// Local node renderers.
|
||||||
const NODES = {
|
const NODES = {
|
||||||
'block-quote': (props) => <blockquote {...props.attributes}>{props.children}</blockquote>,
|
'block-quote': (props) => <Block type='blockquote' {...props.attributes}>{props.children}</Block>,
|
||||||
'bulleted-list': props => <ul {...props.attributes}>{props.children}</ul>,
|
'bulleted-list': props => <Block type='Unordered List'><ul {...props.attributes}>{props.children}</ul></Block>,
|
||||||
'heading1': props => <h1 {...props.attributes}>{props.children}</h1>,
|
'heading1': props => <Block type='Heading1' {...props.attributes}>{props.children}</Block>,
|
||||||
'heading2': props => <h2 {...props.attributes}>{props.children}</h2>,
|
'heading2': props => <Block type='Heading2' {...props.attributes}>{props.children}</Block>,
|
||||||
|
'heading3': props => <Block type='Heading2' {...props.attributes}>{props.children}</Block>,
|
||||||
|
'heading4': props => <Block type='Heading2' {...props.attributes}>{props.children}</Block>,
|
||||||
|
'heading5': props => <Block type='Heading2' {...props.attributes}>{props.children}</Block>,
|
||||||
|
'heading6': props => <Block type='Heading2' {...props.attributes}>{props.children}</Block>,
|
||||||
'list-item': props => <li {...props.attributes}>{props.children}</li>,
|
'list-item': props => <li {...props.attributes}>{props.children}</li>,
|
||||||
'paragraph': props => <p {...props.attributes}>{props.children}</p>,
|
'paragraph': props => <Block type='Paragraph' {...props.attributes}>{props.children}</Block>,
|
||||||
'link': (props) => {
|
'link': (props) => {
|
||||||
const { data } = props.node;
|
const { data } = props.node;
|
||||||
const href = data.get('href');
|
const href = data.get('href');
|
||||||
return <a {...props.attributes} href={href}>{props.children}</a>;
|
return <span><a {...props.attributes} href={href}>{props.children}</a><Icon type="link"/></span>;
|
||||||
},
|
},
|
||||||
'image': (props) => {
|
'image': (props) => {
|
||||||
const { node, state } = props;
|
const { node, state } = props;
|
||||||
@ -52,8 +57,9 @@ const MARKS = {
|
|||||||
class MarkdownControl extends React.Component {
|
class MarkdownControl extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.blockEdit = false;
|
||||||
this.state = {
|
this.state = {
|
||||||
state: markdown.deserialize(props.value || '')
|
state: props.value ? markdown.deserialize(props.value) : Plain.deserialize('')
|
||||||
};
|
};
|
||||||
|
|
||||||
this.hasMark = this.hasMark.bind(this);
|
this.hasMark = this.hasMark.bind(this);
|
||||||
@ -62,6 +68,7 @@ class MarkdownControl extends React.Component {
|
|||||||
this.handleDocumentChange = this.handleDocumentChange.bind(this);
|
this.handleDocumentChange = this.handleDocumentChange.bind(this);
|
||||||
this.onClickMark = this.onClickMark.bind(this);
|
this.onClickMark = this.onClickMark.bind(this);
|
||||||
this.onClickBlock = this.onClickBlock.bind(this);
|
this.onClickBlock = this.onClickBlock.bind(this);
|
||||||
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||||
this.renderToolbar = this.renderToolbar.bind(this);
|
this.renderToolbar = this.renderToolbar.bind(this);
|
||||||
this.renderMarkButton = this.renderMarkButton.bind(this);
|
this.renderMarkButton = this.renderMarkButton.bind(this);
|
||||||
this.renderBlockButton = this.renderBlockButton.bind(this);
|
this.renderBlockButton = this.renderBlockButton.bind(this);
|
||||||
@ -88,8 +95,12 @@ class MarkdownControl extends React.Component {
|
|||||||
* content changes
|
* content changes
|
||||||
*/
|
*/
|
||||||
handleChange(state) {
|
handleChange(state) {
|
||||||
|
if (this.blockEdit) {
|
||||||
|
this.blockEdit = false;
|
||||||
|
} else {
|
||||||
this.setState({ state });
|
this.setState({ state });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleDocumentChange(document, state) {
|
handleDocumentChange(document, state) {
|
||||||
this.props.onChange(markdown.serialize(state));
|
this.props.onChange(markdown.serialize(state));
|
||||||
@ -100,7 +111,6 @@ class MarkdownControl extends React.Component {
|
|||||||
* Toggle marks / blocks when button is clicked
|
* Toggle marks / blocks when button is clicked
|
||||||
*/
|
*/
|
||||||
onClickMark(e, type) {
|
onClickMark(e, type) {
|
||||||
e.preventDefault();
|
|
||||||
let { state } = this.state;
|
let { state } = this.state;
|
||||||
|
|
||||||
state = state
|
state = state
|
||||||
@ -111,6 +121,19 @@ class MarkdownControl extends React.Component {
|
|||||||
this.setState({ state });
|
this.setState({ state });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleKeyDown(evt) {
|
||||||
|
if (evt.shiftKey && evt.key === 'Enter') {
|
||||||
|
this.blockEdit = true;
|
||||||
|
let { state } = this.state;
|
||||||
|
state = state
|
||||||
|
.transform()
|
||||||
|
.insertText(' \n')
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
this.setState({ state });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onClickBlock(e, type) {
|
onClickBlock(e, type) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let { state } = this.state;
|
let { state } = this.state;
|
||||||
@ -167,6 +190,7 @@ class MarkdownControl extends React.Component {
|
|||||||
{this.renderMarkButton('bold', 'b')}
|
{this.renderMarkButton('bold', 'b')}
|
||||||
{this.renderMarkButton('italic', 'i')}
|
{this.renderMarkButton('italic', 'i')}
|
||||||
{this.renderMarkButton('code', 'code')}
|
{this.renderMarkButton('code', 'code')}
|
||||||
|
{this.renderMarkButton('linebreak', 'break')}
|
||||||
{this.renderBlockButton('heading1', 'h1')}
|
{this.renderBlockButton('heading1', 'h1')}
|
||||||
{this.renderBlockButton('heading2', 'h2')}
|
{this.renderBlockButton('heading2', 'h2')}
|
||||||
{this.renderBlockButton('block-quote', 'blockquote')}
|
{this.renderBlockButton('block-quote', 'blockquote')}
|
||||||
@ -219,6 +243,7 @@ class MarkdownControl extends React.Component {
|
|||||||
renderNode={this.renderNode}
|
renderNode={this.renderNode}
|
||||||
renderMark={this.renderMark}
|
renderMark={this.renderMark}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
onDocumentChange={this.handleDocumentChange}
|
onDocumentChange={this.handleDocumentChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
71
src/components/Widgets/MarkdownControlElements/Block.css
Normal file
71
src/components/Widgets/MarkdownControlElements/Block.css
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
.root {
|
||||||
|
border: dotted 1px #ddd;
|
||||||
|
position: relative;
|
||||||
|
margin: 9px 0 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.type:after {
|
||||||
|
content: attr(data-type);
|
||||||
|
font-size: 10px;
|
||||||
|
color: #aaa;
|
||||||
|
position: absolute;
|
||||||
|
top : -7px;;
|
||||||
|
margin-left: 1em;
|
||||||
|
padding: 0 3px;
|
||||||
|
display: inline;
|
||||||
|
background-color: #fafafa;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body img{
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Paragraph {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.Heading1, .Heading2, .Heading3, .Heading4, .Heading5, .Heading6 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
.Heading1 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Heading2 {
|
||||||
|
font-size: 1.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Heading3 {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Heading4 {
|
||||||
|
font-size: 1.07em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Heading5 {
|
||||||
|
font-size: 1.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Heading6 {
|
||||||
|
font-size: 1.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockquote {
|
||||||
|
padding-left: 5px;
|
||||||
|
border-left: solid 3px #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
32
src/components/Widgets/MarkdownControlElements/Block.js
Normal file
32
src/components/Widgets/MarkdownControlElements/Block.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
import styles from './Block.css';
|
||||||
|
|
||||||
|
const AVAILABLE_TYPES = [
|
||||||
|
'Paragraph',
|
||||||
|
'Heading1',
|
||||||
|
'Heading2',
|
||||||
|
'Heading3',
|
||||||
|
'Heading4',
|
||||||
|
'Heading5',
|
||||||
|
'Heading6',
|
||||||
|
'ul',
|
||||||
|
'blockquote'
|
||||||
|
];
|
||||||
|
|
||||||
|
export function Block({ type, children }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<div contentEditable={false} className={styles.type} data-type={type}/>
|
||||||
|
<div className={`${styles.body} ${styles[type]}`}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Block.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
type: PropTypes.oneOf(AVAILABLE_TYPES).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Block;
|
Loading…
x
Reference in New Issue
Block a user