1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-11 01:33:58 +02:00
This commit is contained in:
Ian Storm Taylor
2016-08-14 13:21:46 -07:00
parent dbcb9e531f
commit ccac6102a5
34 changed files with 260 additions and 557 deletions

View File

@@ -4,21 +4,23 @@ import React from 'react'
import initialState from './state.json' import initialState from './state.json'
/** /**
* Define a set of node renderers. * Define a schema.
* *
* @type {Object} * @type {Object}
*/ */
const NODES = { const schema = {
'block-quote': props => <blockquote>{props.children}</blockquote>, nodes: {
'bulleted-list': props => <ul>{props.children}</ul>, 'block-quote': props => <blockquote>{props.children}</blockquote>,
'heading-one': props => <h1>{props.children}</h1>, 'bulleted-list': props => <ul>{props.children}</ul>,
'heading-two': props => <h2>{props.children}</h2>, 'heading-one': props => <h1>{props.children}</h1>,
'heading-three': props => <h3>{props.children}</h3>, 'heading-two': props => <h2>{props.children}</h2>,
'heading-four': props => <h4>{props.children}</h4>, 'heading-three': props => <h3>{props.children}</h3>,
'heading-five': props => <h5>{props.children}</h5>, 'heading-four': props => <h4>{props.children}</h4>,
'heading-six': props => <h6>{props.children}</h6>, 'heading-five': props => <h5>{props.children}</h5>,
'list-item': props => <li>{props.children}</li> 'heading-six': props => <h6>{props.children}</h6>,
'list-item': props => <li>{props.children}</li>,
}
} }
/** /**
@@ -73,26 +75,15 @@ class AutoMarkdown extends React.Component {
return ( return (
<div className="editor"> <div className="editor">
<Editor <Editor
schema={schema}
state={this.state.state} state={this.state.state}
onChange={this.onChange} onChange={this.onChange}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
/> />
</div> </div>
) )
} }
/**
* Render a `node`.
*
* @param {Node} node
* @return {Element}
*/
renderNode = (node) => {
return NODES[node.type]
}
/** /**
* On change. * On change.
* *

View File

@@ -47,6 +47,44 @@ function CodeBlock(props) {
) )
} }
/**
* Define a Prism.js decorator for code blocks.
*
* @param {Text} text
* @param {Block} block
*/
function codeBlockDecorator(text, block) {
let characters = text.characters.asMutable()
const language = block.data.get('language')
const string = text.text
const grammar = Prism.languages[language]
const tokens = Prism.tokenize(string, grammar)
let offset = 0
for (const token of tokens) {
if (typeof token == 'string') {
offset += token.length
continue
}
const length = offset + token.content.length
const type = `highlight-${token.type}`
for (let i = offset; i < length; i++) {
let char = characters.get(i)
let { marks } = char
marks = marks.add(Mark.create({ type }))
char = char.merge({ marks })
characters = characters.set(i, char)
}
offset = length
}
return characters.asImmutable()
}
/** /**
* Define a schema. * Define a schema.
* *
@@ -57,36 +95,7 @@ const schema = {
nodes: { nodes: {
code: { code: {
component: CodeBlock, component: CodeBlock,
decorator: (block, text) => { decorator: codeBlockDecorator,
let characters = text.characters.asMutable()
const language = block.data.get('language')
const string = text.text
const grammar = Prism.languages[language]
const tokens = Prism.tokenize(string, grammar)
let offset = 0
for (const token of tokens) {
if (typeof token == 'string') {
offset += token.length
continue
}
const length = offset + token.content.length
const type = `highlight-${token.type}`
for (let i = offset; i < length; i++) {
let char = characters.get(i)
let { marks } = char
marks = marks.add(Mark.create({ type }))
char = char.merge({ marks })
characters = characters.set(i, char)
}
offset = length
}
return characters.asImmutable()
}
} }
}, },
marks: { marks: {

View File

@@ -10,41 +10,36 @@ import initialState from './state.json'
const DEFAULT_NODE = 'paragraph' const DEFAULT_NODE = 'paragraph'
/** /**
* Define a set of node renderers. * Define a schema.
* *
* @type {Object} * @type {Object}
*/ */
const NODES = { const schema = {
'block-quote': props => <blockquote {...props.attributes}>{props.children}</blockquote>, nodes: {
'bulleted-list': props => <ul {...props.attributes}>{props.children}</ul>, 'block-quote': props => <blockquote {...props.attributes}>{props.children}</blockquote>,
'heading-one': props => <h1 {...props.attributes}>{props.children}</h1>, 'bulleted-list': props => <ul {...props.attributes}>{props.children}</ul>,
'heading-two': props => <h2 {...props.attributes}>{props.children}</h2>, 'heading-one': props => <h1 {...props.attributes}>{props.children}</h1>,
'list-item': props => <li {...props.attributes}>{props.children}</li>, 'heading-two': props => <h2 {...props.attributes}>{props.children}</h2>,
'numbered-list': props => <ol {...props.attributes}>{props.children}</ol> 'list-item': props => <li {...props.attributes}>{props.children}</li>,
} 'numbered-list': props => <ol {...props.attributes}>{props.children}</ol>
/**
* Define a set of mark renderers.
*
* @type {Object}
*/
const MARKS = {
bold: {
fontWeight: 'bold'
}, },
code: { marks: {
fontFamily: 'monospace', bold: {
backgroundColor: '#eee', fontWeight: 'bold'
padding: '3px', },
borderRadius: '4px' code: {
}, fontFamily: 'monospace',
italic: { backgroundColor: '#eee',
fontStyle: 'italic' padding: '3px',
}, borderRadius: '4px'
underlined: { },
textDecoration: 'underline' italic: {
fontStyle: 'italic'
},
underlined: {
textDecoration: 'underline'
}
} }
} }
@@ -283,9 +278,8 @@ class RichText extends React.Component {
<div className="editor"> <div className="editor">
<Editor <Editor
placeholder={'Enter some rich text...'} placeholder={'Enter some rich text...'}
schema={schema}
state={this.state.state} state={this.state.state}
renderNode={this.renderNode}
renderMark={this.renderMark}
onChange={this.onChange} onChange={this.onChange}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
/> />
@@ -293,28 +287,6 @@ class RichText extends React.Component {
) )
} }
/**
* Return a node renderer for a Slate `node`.
*
* @param {Node} node
* @return {Component or Void}
*/
renderNode = (node) => {
return NODES[node.type]
}
/**
* Return a mark renderer for a Slate `mark`.
*
* @param {Mark} mark
* @return {Object or Void}
*/
renderMark = (mark) => {
return MARKS[mark.type]
}
} }
/** /**

View File

@@ -6,13 +6,15 @@ import Video from './video'
import initialState from './state.json' import initialState from './state.json'
/** /**
* Define a set of node renderers. * Define a schema.
* *
* @type {Object} * @type {Object}
*/ */
const NODES = { const schema = {
video: Video nodes: {
video: Video
}
} }
/** /**
@@ -53,25 +55,14 @@ class Embeds extends React.Component {
return ( return (
<div className="editor"> <div className="editor">
<Editor <Editor
schema={schema}
state={this.state.state} state={this.state.state}
renderNode={this.renderNode}
onChange={this.onChange} onChange={this.onChange}
/> />
</div> </div>
) )
} }
/**
* Render a `node`.
*
* @param {Node} node
* @return {Element}
*/
renderNode = (node) => {
return NODES[node.type]
}
} }
/** /**

View File

@@ -6,26 +6,17 @@ import position from 'selection-position'
import initialState from './state.json' import initialState from './state.json'
/** /**
* Define a set of mark renderers. * Define a schema.
* *
* @type {Object} * @type {Object}
*/ */
const MARKS = { const schema = {
bold: { marks: {
fontWeight: 'bold' bold: props => <strong>{props.children}</strong>,
}, code: props => <code>{props.children}</code>,
code: { italic: props => <em>{props.children}</em>,
fontFamily: 'monospace', underlined: props => <u>{props.children}</u>,
backgroundColor: '#eee',
padding: '3px',
borderRadius: '4px'
},
italic: {
fontStyle: 'italic'
},
underlined: {
textDecoration: 'underline'
} }
} }
@@ -175,25 +166,14 @@ class HoveringMenu extends React.Component {
return ( return (
<div className="editor"> <div className="editor">
<Editor <Editor
schema={schema}
state={this.state.state} state={this.state.state}
renderMark={this.renderMark}
onChange={this.onChange} onChange={this.onChange}
/> />
</div> </div>
) )
} }
/**
* Return a mark renderer for a Slate `mark`.
*
* @param {Mark} mark
* @return {Object or Void}
*/
renderMark = (mark) => {
return MARKS[mark.type]
}
/** /**
* Update the menu's absolute position. * Update the menu's absolute position.
*/ */

View File

@@ -20,28 +20,23 @@ injector()
const DEFAULT_NODE = 'paragraph' const DEFAULT_NODE = 'paragraph'
/** /**
* Define a set of node renderers. * Define a schema.
* *
* @type {Object} * @type {Object}
*/ */
const NODES = { const schema = {
'block-code': props => <pre><code {...props.attributes}>{props.children}</code></pre>, nodes: {
'block-quote': props => <blockquote {...props.attributes}>{props.children}</blockquote>, 'block-code': props => <pre><code {...props.attributes}>{props.children}</code></pre>,
'heading-two': props => <h2 {...props.attributes}>{props.children}</h2>, 'block-quote': props => <blockquote {...props.attributes}>{props.children}</blockquote>,
'paragraph': props => <p {...props.attributes}>{props.children}</p> 'heading-two': props => <h2 {...props.attributes}>{props.children}</h2>,
} 'paragraph': props => <p {...props.attributes}>{props.children}</p>,
},
/** marks: {
* Define a set of mark renderers. bold: props => <strong>{props.children}</strong>,
* highlight: props => <mark>{props.children}</mark>,
* @type {Object} italic: props => <em>{props.children}</em>,
*/ }
const MARKS = {
bold: props => <strong>{props.children}</strong>,
highlight: props => <mark>{props.children}</mark>,
italic: props => <em>{props.children}</em>,
} }
/** /**
@@ -274,37 +269,14 @@ class Iframes extends React.Component {
return ( return (
<Editor <Editor
placeholder={'Enter some rich text...'} placeholder={'Enter some rich text...'}
schema={schema}
state={this.state.state} state={this.state.state}
renderNode={this.renderNode}
renderMark={this.renderMark}
onChange={this.onChange} onChange={this.onChange}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
/> />
) )
} }
/**
* Return a node renderer for a Slate `node`.
*
* @param {Node} node
* @return {Component or Void}
*/
renderNode = (node) => {
return NODES[node.type]
}
/**
* Return a mark renderer for a Slate `mark`.
*
* @param {Mark} mark
* @return {Object or Void}
*/
renderMark = (mark) => {
return MARKS[mark.type]
}
} }
export default Iframes export default Iframes

View File

@@ -7,20 +7,22 @@ import isImage from 'is-image'
import isUrl from 'is-url' import isUrl from 'is-url'
/** /**
* Define a set of node renderers. * Define a schema.
* *
* @type {Object} * @type {Object}
*/ */
const NODES = { const schema = {
image: (props) => { nodes: {
const { node, state } = props image: (props) => {
const isFocused = state.selection.hasEdgeIn(node) const { node, state } = props
const src = node.data.get('src') const isFocused = state.selection.hasEdgeIn(node)
const className = isFocused ? 'active' : null const src = node.data.get('src')
return ( const className = isFocused ? 'active' : null
<img src={src} className={className} {...props.attributes} /> return (
) <img src={src} className={className} {...props.attributes} />
)
}
} }
} }
@@ -83,8 +85,8 @@ class Images extends React.Component {
return ( return (
<div className="editor"> <div className="editor">
<Editor <Editor
schema={schema}
state={this.state.state} state={this.state.state}
renderNode={this.renderNode}
onChange={this.onChange} onChange={this.onChange}
onDocumentChange={this.onDocumentChange} onDocumentChange={this.onDocumentChange}
onDrop={this.onDrop} onDrop={this.onDrop}
@@ -94,17 +96,6 @@ class Images extends React.Component {
) )
} }
/**
* Render a `node`.
*
* @param {Node} node
* @return {Element}
*/
renderNode = (node) => {
return NODES[node.type]
}
/** /**
* On change. * On change.
* *

View File

@@ -21,6 +21,11 @@ pre {
white-space: pre-wrap; white-space: pre-wrap;
} }
:not(pre) > code {
background-color: #eee;
padding: 3px;
}
img { img {
max-width: 100%; max-width: 100%;
max-height: 20em; max-height: 20em;

View File

@@ -7,17 +7,19 @@ import isUrl from 'is-url'
import { Map } from 'immutable' import { Map } from 'immutable'
/** /**
* Define a set of node renderers. * Define a schema.
* *
* @type {Object} * @type {Object}
*/ */
const NODES = { const schema = {
paragraph: props => <p>{props.children}</p>, nodes: {
link: (props) => { paragraph: props => <p>{props.children}</p>,
const { data } = props.node link: (props) => {
const href = data.get('href') const { data } = props.node
return <a {...props.attributes} href={href}>{props.children}</a> const href = data.get('href')
return <a {...props.attributes} href={href}>{props.children}</a>
}
} }
} }
@@ -181,8 +183,8 @@ class Links extends React.Component {
return ( return (
<div className="editor"> <div className="editor">
<Editor <Editor
schema={schema}
state={this.state.state} state={this.state.state}
renderNode={this.renderNode}
onChange={this.onChange} onChange={this.onChange}
onPaste={this.onPaste} onPaste={this.onPaste}
/> />
@@ -190,17 +192,6 @@ class Links extends React.Component {
) )
} }
/**
* Render a `node`.
*
* @param {Node} node
* @return {Element}
*/
renderNode = (node) => {
return NODES[node.type]
}
} }
/** /**

View File

@@ -4,51 +4,35 @@ import React from 'react'
import initialState from './state.json' import initialState from './state.json'
/** /**
* Define a set of node renderers. * Define a schema.
* *
* @type {Object} * @type {Object}
*/ */
const NODES = { const schema = {
'bulleted-list': props => <ul {...props.attributes}>{props.children}</ul>, nodes: {
'code': props => <pre><code {...props.attributes}>{props.children}</code></pre>, 'bulleted-list': props => <ul {...props.attributes}>{props.children}</ul>,
'heading-one': props => <h1 {...props.attributes}>{props.children}</h1>, 'code': props => <pre><code {...props.attributes}>{props.children}</code></pre>,
'heading-two': props => <h2 {...props.attributes}>{props.children}</h2>, 'heading-one': props => <h1 {...props.attributes}>{props.children}</h1>,
'heading-three': props => <h3 {...props.attributes}>{props.children}</h3>, 'heading-two': props => <h2 {...props.attributes}>{props.children}</h2>,
'heading-four': props => <h4 {...props.attributes}>{props.children}</h4>, 'heading-three': props => <h3 {...props.attributes}>{props.children}</h3>,
'heading-five': props => <h5 {...props.attributes}>{props.children}</h5>, 'heading-four': props => <h4 {...props.attributes}>{props.children}</h4>,
'heading-six': props => <h6 {...props.attributes}>{props.children}</h6>, 'heading-five': props => <h5 {...props.attributes}>{props.children}</h5>,
'list-item': props => <li {...props.attributes}>{props.children}</li>, 'heading-six': props => <h6 {...props.attributes}>{props.children}</h6>,
'numbered-list': props => <ol {...props.attributes}>{props.children}</ol>, 'list-item': props => <li {...props.attributes}>{props.children}</li>,
'quote': props => <blockquote {...props.attributes}>{props.children}</blockquote>, 'numbered-list': props => <ol {...props.attributes}>{props.children}</ol>,
'link': (props) => { 'quote': props => <blockquote {...props.attributes}>{props.children}</blockquote>,
const { data } = props.node 'link': (props) => {
const href = data.get('href') const { data } = props.node
return <a href={href} {...props.attributes}>{props.children}</a> const href = data.get('href')
} return <a href={href} {...props.attributes}>{props.children}</a>
} }
/**
* Define a set of mark renderers.
*
* @type {Object}
*/
const MARKS = {
bold: {
fontWeight: 'bold'
}, },
code: { marks: {
fontFamily: 'monospace', bold: props => <strong>{props.children}</strong>,
backgroundColor: '#eee', code: props => <code>{props.children}</code>,
padding: '3px', italic: props => <em>{props.children}</em>,
borderRadius: '4px' underlined: props => <u>{props.children}</u>,
},
italic: {
fontStyle: 'italic'
},
underlined: {
textDecoration: 'underline'
} }
} }
@@ -212,9 +196,8 @@ class PasteHtml extends React.Component {
return ( return (
<div className="editor"> <div className="editor">
<Editor <Editor
schema={schema}
state={this.state.state} state={this.state.state}
renderNode={this.renderNode}
renderMark={this.renderMark}
onPaste={this.onPaste} onPaste={this.onPaste}
onChange={this.onChange} onChange={this.onChange}
/> />
@@ -222,28 +205,6 @@ class PasteHtml extends React.Component {
) )
} }
/**
* Return a node renderer for a Slate `node`.
*
* @param {Node} node
* @return {Component or Void}
*/
renderNode = (node) => {
return NODES[node.type]
}
/**
* Return a mark renderer for a Slate `mark`.
*
* @param {Mark} mark
* @return {Object or Void}
*/
renderMark = (mark) => {
return MARKS[mark.type]
}
} }
/** /**

View File

@@ -299,7 +299,6 @@ class RichText extends React.Component {
placeholder={'Enter some rich text...'} placeholder={'Enter some rich text...'}
schema={schema} schema={schema}
state={this.state.state} state={this.state.state}
renderMark={this.renderMark}
onChange={this.onChange} onChange={this.onChange}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
/> />
@@ -307,17 +306,6 @@ class RichText extends React.Component {
) )
} }
/**
* Return a mark renderer for a Slate `mark`.
*
* @param {Mark} mark
* @return {Object or Void}
*/
renderMark = (mark) => {
return MARKS[mark.type]
}
} }
/** /**

View File

@@ -5,13 +5,15 @@ import SoftBreak from 'slate-soft-break'
import initialState from './state.json' import initialState from './state.json'
/** /**
* Define a set of node renderers. * Define a schema.
* *
* @type {Object} * @type {Object}
*/ */
const NODES = { const schema = {
'block-quote': (props) => <blockquote {...props.attributes}>{props.children}</blockquote>, nodes: {
'block-quote': (props) => <blockquote {...props.attributes}>{props.children}</blockquote>,
}
} }
/** /**
@@ -69,25 +71,14 @@ class PlainText extends React.Component {
return ( return (
<Editor <Editor
placeholder={'Enter some plain text...'} placeholder={'Enter some plain text...'}
schema={schema}
state={this.state.state}
onChange={this.onChange} onChange={this.onChange}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
state={this.state.state}
/> />
) )
} }
/**
* Return a node renderer for a Slate `node`.
*
* @param {Node} node
* @return {Component or Void}
*/
renderNode = (node) => {
return NODES[node.type]
}
} }
/** /**

View File

@@ -5,26 +5,19 @@ import initialState from './state.json'
import keycode from 'keycode' import keycode from 'keycode'
/** /**
* Define a set of node renderers. * Define a schema.
* *
* @type {Object} * @type {Object}
*/ */
const NODES = { const schema = {
'table': props => <table><tbody {...props.attributes}>{props.children}</tbody></table>, nodes: {
'table-row': props => <tr {...props.attributes}>{props.children}</tr>, 'table': props => <table><tbody {...props.attributes}>{props.children}</tbody></table>,
'table-cell': props => <td {...props.attributes}>{props.children}</td> 'table-row': props => <tr {...props.attributes}>{props.children}</tr>,
} 'table-cell': props => <td {...props.attributes}>{props.children}</td>,
},
/** marks: {
* Define a set of mark renderers. 'bold': props => <strong>{props.children}</strong>
*
* @type {Object}
*/
const MARKS = {
bold: {
fontWeight: 'bold'
} }
} }
@@ -125,9 +118,8 @@ class Tables extends React.Component {
return ( return (
<div className="editor"> <div className="editor">
<Editor <Editor
schema={schema}
state={this.state.state} state={this.state.state}
renderNode={this.renderNode}
renderMark={this.renderMark}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
onChange={this.onChange} onChange={this.onChange}
/> />
@@ -135,28 +127,6 @@ class Tables extends React.Component {
) )
} }
/**
* Return a node renderer for a Slate `node`.
*
* @param {Node} node
* @return {Component or Void}
*/
renderNode = (node) => {
return NODES[node.type]
}
/**
* Return a mark renderer for a Slate `mark`.
*
* @param {Mark} mark
* @return {Object or Void}
*/
renderMark = (mark) => {
return MARKS[mark.type]
}
} }
/** /**

View File

@@ -54,7 +54,7 @@ class Editor extends React.Component {
static propTypes = { static propTypes = {
className: React.PropTypes.string, className: React.PropTypes.string,
onBeforeChange: React.PropTypes.func, onBeforeChange: React.PropTypes.func,
onChange: React.PropTypes.func.isRequired, onChange: React.PropTypes.func,
onDocumentChange: React.PropTypes.func, onDocumentChange: React.PropTypes.func,
onSelectionChange: React.PropTypes.func, onSelectionChange: React.PropTypes.func,
placeholder: React.PropTypes.any, placeholder: React.PropTypes.any,
@@ -73,6 +73,7 @@ class Editor extends React.Component {
*/ */
static defaultProps = { static defaultProps = {
onChange: noop,
onDocumentChange: noop, onDocumentChange: noop,
onSelectionChange: noop, onSelectionChange: noop,
plugins: [], plugins: [],

View File

@@ -31,7 +31,8 @@ class Void extends React.Component {
children: React.PropTypes.any.isRequired, children: React.PropTypes.any.isRequired,
editor: React.PropTypes.object.isRequired, editor: React.PropTypes.object.isRequired,
node: React.PropTypes.object.isRequired, node: React.PropTypes.object.isRequired,
state: React.PropTypes.object.isRequired schema: React.PropTypes.object.isRequired,
state: React.PropTypes.object.isRequired,
}; };
/** /**
@@ -117,7 +118,7 @@ class Void extends React.Component {
*/ */
renderLeaf = () => { renderLeaf = () => {
const { node, state } = this.props const { node, schema, state } = this.props
const child = node.getTexts().first() const child = node.getTexts().first()
const ranges = child.getRanges() const ranges = child.getRanges()
const text = '' const text = ''
@@ -133,6 +134,7 @@ class Void extends React.Component {
isVoid isVoid
renderMark={noop} renderMark={noop}
key={offsetKey} key={offsetKey}
schema={schema}
state={state} state={state}
node={child} node={child}
ranges={ranges} ranges={ranges}

View File

@@ -1,92 +0,0 @@
/**
* The default Slate schema rules, which enforce the most basic constraints.
*
* @type {Array}
*/
const RULES = [
{
match: {
kind: 'document'
},
validate: {
anyOf: [
{ kind: 'block' }
]
},
transform: (transform, match, reason) => {
return reason.value.reduce((tr, node) => {
return tr.removeNodeByKey(node.key)
}, transform)
}
},
{
match: {
kind: 'block'
},
validate: {
anyOf: [
{ kind: 'block' },
{ kind: 'inline' },
{ kind: 'text' },
]
},
transform: (transform, match, reason) => {
return reason.value.reduce((tr, node) => {
return tr.removeNodeByKey(node.key)
}, transform)
}
},
{
match: {
kind: 'inline'
},
validate: {
anyOf: [
{ kind: 'inline' },
{ kind: 'text' },
]
},
transform: (transform, match, reason) => {
return reason.value.reduce((tr, node) => {
return tr.removeNodeByKey(node.key)
}, transform)
}
},
// {
// match: { isVoid: true },
// validate: {
// text: ' '
// },
// transform: (transform, node) => {
// const { state } = transform
// const range = state.selection.moveToRangeOf(node)
// return transform.delete().insertText(' ')
// }
// },
// {
// match: (object) => {
// return (
// object.kind == 'block' &&
// object.nodes.size == 1 &&
// object.nodes.first().isVoid
// )
// },
// invalid: true,
// transform: (transform, node) => {
// const child = node.nodes.first()
// const text =
// return transform
// .insertNodeBeforeNodeByKey(child.)
// }
// }
]
/**
* Export.
*
* @type {Array}
*/
export default RULES

View File

@@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import RULES from '../constants/rules'
import includes from 'lodash/includes' import includes from 'lodash/includes'
import isReactComponent from '../utils/is-react-component' import isReactComponent from '../utils/is-react-component'
import typeOf from 'type-of' import typeOf from 'type-of'
@@ -171,7 +170,7 @@ class Schema extends new Record(DEFAULTS) {
.filter(rule => rule.match(object) && rule.decorator) .filter(rule => rule.match(object) && rule.decorator)
.map((rule) => { .map((rule) => {
return (text) => { return (text) => {
return rule.decorator(object, text) return rule.decorator(text, object)
} }
}) })
} }
@@ -250,7 +249,7 @@ function normalizeNodes(nodes) {
type: key, type: key,
} }
if (value.component) { if (value.component || value.decorator || value.validate) {
rules.push({ rules.push({
match, match,
...value, ...value,
@@ -283,7 +282,7 @@ function normalizeMarks(marks) {
type: key, type: key,
} }
if (value.component) { if (value.component || value.decorator || value.validate) {
rules.push({ rules.push({
match, match,
...value, ...value,
@@ -392,9 +391,9 @@ function normalizeTransform(transform) {
function normalizeSpec(obj, giveReason) { function normalizeSpec(obj, giveReason) {
const spec = { ...obj } const spec = { ...obj }
if (spec.exactlyOf) spec.exactlyOf = spec.exactlyOf.map(normalizeSpec) if (spec.exactlyOf) spec.exactlyOf = spec.exactlyOf.map(s => normalizeSpec(s))
if (spec.anyOf) spec.anyOf = spec.anyOf.map(normalizeSpec) if (spec.anyOf) spec.anyOf = spec.anyOf.map(s => normalizeSpec(s))
if (spec.noneOf) spec.noneOf = spec.noneOf.map(normalizeSpec) if (spec.noneOf) spec.noneOf = spec.noneOf.map(s => normalizeSpec(s))
return (node) => { return (node) => {
for (const key in CHECKS) { for (const key in CHECKS) {

View File

@@ -37,6 +37,7 @@
"browserify-shim": "^3.8.12", "browserify-shim": "^3.8.12",
"disc": "^1.3.2", "disc": "^1.3.2",
"envify": "^3.4.1", "envify": "^3.4.1",
"enzyme": "^2.4.1",
"eslint": "^3.0.1", "eslint": "^3.0.1",
"eslint-plugin-import": "^1.10.2", "eslint-plugin-import": "^1.10.2",
"eslint-plugin-react": "^5.2.2", "eslint-plugin-react": "^5.2.2",
@@ -46,10 +47,12 @@
"is-image": "^1.0.1", "is-image": "^1.0.1",
"is-url": "^1.2.2", "is-url": "^1.2.2",
"mocha": "^2.5.3", "mocha": "^2.5.3",
"mocha-jsdom": "^1.1.0",
"npm-run-all": "^2.3.0", "npm-run-all": "^2.3.0",
"prismjs": "^1.5.1", "prismjs": "^1.5.1",
"react": "^15.2.0", "react": "^15.2.0",
"react-addons-perf": "^15.2.1", "react-addons-perf": "^15.2.1",
"react-addons-test-utils": "^15.3.0",
"react-dom": "^15.1.0", "react-dom": "^15.1.0",
"react-frame-aware-selection-plugin": "0.0.2", "react-frame-aware-selection-plugin": "0.0.2",
"react-frame-component": "^0.6.2", "react-frame-component": "^0.6.2",

View File

@@ -7,6 +7,8 @@ function Image(props) {
) )
} }
export function renderNode(node) { export const schema = {
if (node.type == 'image') return Image nodes: {
image: Image
}
} }

View File

@@ -5,6 +5,8 @@ function Code(props) {
return <pre {...props.attributes}><code>{props.children}</code></pre> return <pre {...props.attributes}><code>{props.children}</code></pre>
} }
export function renderNode(node) { export const schema = {
if (node.type == 'code') return Code nodes: {
code: Code
}
} }

View File

@@ -1,16 +1,11 @@
import React from 'react'
import { Mark } from '../../../..' import { Mark } from '../../../..'
const BOLD = { const BOLD = {
fontWeight: 'bold' fontWeight: 'bold'
} }
export function renderMark(mark) { function decorator(text) {
if (mark.type == 'bold') return BOLD
}
export function renderDecorations(text) {
let { characters } = text let { characters } = text
let second = characters.get(1) let second = characters.get(1)
let mark = Mark.create({ type: 'bold' }) let mark = Mark.create({ type: 'bold' })
@@ -19,3 +14,14 @@ export function renderDecorations(text) {
characters = characters.set(1, second) characters = characters.set(1, second)
return characters return characters
} }
export const schema = {
nodes: {
default: {
decorator
}
},
marks: {
bold: BOLD
}
}

View File

@@ -6,6 +6,8 @@ function Link(props) {
return <a {...props.attributes} href={href}>{props.children}</a> return <a {...props.attributes} href={href}>{props.children}</a>
} }
export function renderNode(node) { export const schema = {
if (node.type == 'link') return Link nodes: {
link: Link
}
} }

View File

@@ -1,29 +0,0 @@
import React from 'react'
const BOLD = {
fontWeight: 'bold'
}
const ITALIC = {
fontStyle: 'italic'
}
const BOLD_ITALIC = {
fontFamily: 'bold-italic'
}
export function renderMark(mark, marks) {
if (
marks.size > 1 &&
marks.some(m => m.type == 'bold') &&
marks.some(m => m.type == 'italic')
) {
return mark.type == 'bold'
? BOLD_ITALIC
: null
}
if (mark.type == 'bold') return BOLD
if (mark.type == 'italic') return ITALIC
}

View File

@@ -1,17 +0,0 @@
nodes:
- kind: block
type: default
nodes:
- kind: text
ranges:
- text: one
marks:
- type: bold
- text: two
marks:
- type: italic
- text: three
marks:
- type: bold
- type: italic

View File

@@ -1,10 +0,0 @@
<div contenteditable="true">
<div style="position:relative;">
<span>
<span><span style="font-weight:bold;">one</span></span>
<span><span style="font-style:italic;">two</span></span>
<span><span style="font-family:bold-italic;">three</span></span>
</span>
</div>
</div>

View File

@@ -7,6 +7,8 @@ class Bold extends React.Component {
} }
} }
export function renderMark(mark) { export const schema = {
if (mark.type == 'bold') return Bold marks: {
bold: Bold
}
} }

View File

@@ -1,10 +1,12 @@
import React from 'react' import React from 'react'
function BOLD(props) { function Bold(props) {
return <strong>{props.children}</strong> return <strong>{props.children}</strong>
} }
export function renderMark(mark) { export const schema = {
if (mark.type == 'bold') return BOLD marks: {
bold: Bold
}
} }

View File

@@ -9,7 +9,9 @@ function ITALIC(props) {
return <i>{props.children}</i> return <i>{props.children}</i>
} }
export function renderMark(mark, marks) { export const schema = {
if (mark.type == 'bold') return BOLD marks: {
if (mark.type == 'italic') return ITALIC bold: BOLD,
italic: ITALIC
}
} }

View File

@@ -1,9 +1,9 @@
import React from 'react' import React from 'react'
export function renderMark(mark) { export const schema = {
if (mark.type == 'bold') { marks: {
return { bold: {
fontWeight: 'bold' fontWeight: 'bold'
} }
} }

View File

@@ -1,6 +1,8 @@
import React from 'react' import React from 'react'
export function renderMark(mark) { export const schema = {
if (mark.type == 'bold') return 'bold' marks: {
bold: 'bold'
}
} }

View File

@@ -5,6 +5,8 @@ function Code(props) {
return <pre {...props.attributes}><code>{props.children}</code></pre> return <pre {...props.attributes}><code>{props.children}</code></pre>
} }
export function renderNode(node) { export const schema = {
if (node.type == 'code') return Code nodes: {
code: Code
}
} }

View File

@@ -6,6 +6,8 @@ function Link(props) {
return <a {...props.attributes} href={href}>{props.children}</a> return <a {...props.attributes} href={href}>{props.children}</a>
} }
export function renderNode(node) { export const schema = {
if (node.type == 'link') return Link nodes: {
link: Link
}
} }

View File

@@ -1,17 +1,21 @@
import React from 'react' import React from 'react'
import fs from 'fs' import fs from 'fs'
import strip from '../helpers/strip-dynamic' import jsdom from 'mocha-jsdom'
import readMetadata from 'read-metadata' import readMetadata from 'read-metadata'
import strip from '../helpers/strip-dynamic'
import { Raw, Editor, Schema } from '../..' import { Raw, Editor, Schema } from '../..'
import { strictEqual } from '../helpers/assert-json' import { mount } from 'enzyme'
import { resolve } from 'path' import { resolve } from 'path'
import { strictEqual } from '../helpers/assert-json'
/** /**
* Tests. * Tests.
*/ */
describe('schema', () => { describe('schema', () => {
jsdom()
const tests = fs.readdirSync(resolve(__dirname, './fixtures')) const tests = fs.readdirSync(resolve(__dirname, './fixtures'))
for (const test of tests) { for (const test of tests) {
@@ -22,10 +26,16 @@ describe('schema', () => {
const input = readMetadata.sync(resolve(dir, 'input.yaml')) const input = readMetadata.sync(resolve(dir, 'input.yaml'))
const expected = readMetadata.sync(resolve(dir, 'output.yaml')) const expected = readMetadata.sync(resolve(dir, 'output.yaml'))
const schema = Schema.create(require(dir)) const schema = Schema.create(require(dir))
const state = Raw.deserialize(input, { terse: true }) const state = Raw.deserialize(input, { terse: true })
const editor = <Editor state={state} schema={schema} /> const props = {
const output = Raw.serialize(state, { terse: true }) onChange: value => value,
schema,
state,
}
const wrapper = mount(<Editor {...props} />)
const normalized = wrapper.state().state
const output = Raw.serialize(normalized, { terse: true })
strictEqual(strip(output), strip(expected)) strictEqual(strip(output), strip(expected))
}) })
} }