1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-09-02 11:42:53 +02:00

add linting

This commit is contained in:
Ian Storm Taylor
2016-07-06 20:19:19 -07:00
parent b61f416e02
commit 226b6592dc
23 changed files with 321 additions and 178 deletions

128
.eslintrc Normal file
View File

@@ -0,0 +1,128 @@
{
"env": {
"browser": true,
"node": true
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}
},
"plugins": [
"import",
"react"
],
"rules": {
"block-spacing": "error",
"comma-dangle": ["error", "only-multiline"],
"comma-spacing": ["error", { "before": false, "after": true }],
"comma-style": ["error", "last"],
"computed-property-spacing": ["error", "never"],
"constructor-super": "error",
"curly": ["error", "multi-line"],
"dot-location": ["error", "property"],
"dot-notation": ["error", { "allowKeywords": true }],
"eol-last": "error",
"func-style": ["error", "declaration"],
"import/default": "error",
"import/export": "error",
"import/named": "error",
"import/namespace": "error",
"import/newline-after-import": "error",
"import/no-deprecated": "error",
"import/no-extraneous-dependencies": "error",
"import/no-mutable-exports": "error",
"import/no-named-as-default": "error",
"import/no-named-as-default-member": "error",
"import/no-unresolved": "error",
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
"lines-around-comment": ["error", { "beforeBlockComment": true, "afterBlockComment": true }],
"new-parens": "error",
"no-array-constructor": "error",
"no-class-assign": "error",
"no-console": "warn",
"no-const-assign": "error",
"no-debugger": "warn",
"no-dupe-args": "error",
"no-dupe-class-members": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-pattern": "error",
"no-ex-assign": "error",
"no-extend-native": "error",
"no-func-assign": "error",
"no-invalid-regexp": "error",
"no-mixed-spaces-and-tabs": ["error", false],
"no-multi-spaces": "error",
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1 }],
"no-native-reassign": "error",
"no-negated-in-lhs": "error",
"no-new-object": "error",
"no-new-symbol": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-sequences": "error",
"no-shadow": "error",
"no-shadow-restricted-names": "error",
"no-spaced-func": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef": "error",
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unused-expressions": "error",
"no-useless-call": "error",
"no-useless-computed-key": "error",
"no-useless-constructor": "error",
"no-useless-rename": "error",
"no-var": "error",
"no-void": "error",
"no-whitespace-before-property": "error",
"no-with": "error",
"object-property-newline": ["error", { "allowMultiplePropertiesPerLine": true }],
"object-shorthand": ["error", "always"],
"padded-blocks": ["error", "never"],
"prefer-arrow-callback": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
"radix": "error",
"react/jsx-boolean-value": ["error", "never"],
"react/jsx-closing-bracket-location": "error",
"react/jsx-curly-spacing": ["error", "never"],
"react/jsx-equals-spacing": "error",
"react/jsx-first-prop-new-line": ["error", "multiline"],
"react/jsx-key": "error",
"react/jsx-no-bind": "error",
"react/jsx-no-duplicate-props": "error",
"react/jsx-no-undef": "error",
"react/jsx-space-before-closing": ["error", "always"],
"react/no-deprecated": "error",
"react/no-did-mount-set-state": "error",
"react/no-did-update-set-state": "error",
"react/no-string-refs": "error",
"react/no-unknown-property": "error",
"react/react-in-jsx-scope": "error",
"react/self-closing-comp": "error",
"react/sort-prop-types": "error",
"react/wrap-multilines": "error",
"rest-spread-spacing": ["error", "never"],
"semi": ["error", "never"],
"space-before-blocks": "error",
"space-before-function-paren": ["error", { "anonymous": "always", "named": "never" }],
"space-in-parens": "error",
"space-infix-ops": "error",
"space-unary-ops": ["error", { "words": true, "nonwords": false }],
"spaced-comment": ["error", "always", { "exceptions": ["-"]}],
"template-curly-spacing": "error",
"unicode-bom": ["error", "never"],
"use-isnan": "error",
"valid-jsdoc": ["error", { "prefer": { "return": "returns" }, "requireReturn": false }],
"valid-typeof": "error",
"yield-star-spacing": ["error", "after"],
"yoda": ["error", "never"]
}
}

View File

@@ -4,7 +4,7 @@ bin = ./node_modules/.bin
babel = $(bin)/babel babel = $(bin)/babel
browserify = $(bin)/browserify browserify = $(bin)/browserify
exorcist = $(bin)/exorcist exorcist = $(bin)/exorcist
standard = $(bin)/standard eslint = $(bin)/eslint
mocha = $(bin)/mocha mocha = $(bin)/mocha
mocha-phantomjs = $(bin)/mocha-phantomjs mocha-phantomjs = $(bin)/mocha-phantomjs
node = node node = node
@@ -20,6 +20,9 @@ ifeq ($(DEBUG),true)
node += debug node += debug
endif endif
# Run all of the checks.
check: lint test
# Remove the generated files. # Remove the generated files.
clean: clean:
@ rm -rf ./dist @ rm -rf ./dist
@@ -46,7 +49,7 @@ install:
# Lint the sources files with Standard JS. # Lint the sources files with Standard JS.
lint: lint:
@ $(standard) ./lib @ $(eslint) "lib/**/*.js"
# Build the test source. # Build the test source.
test/browser/support/build.js: $(shell find ./lib) ./test/browser.js test/browser/support/build.js: $(shell find ./lib) ./test/browser.js
@@ -62,8 +65,6 @@ test: test-browser test-server
test-browser: ./test/support/build.js test-browser: ./test/support/build.js
@ $(mocha-phantomjs) \ @ $(mocha-phantomjs) \
--reporter spec \ --reporter spec \
--timeout 5000 \
--fgrep "$(GREP)" \
./test/support/browser.html ./test/support/browser.html
# Run the server-side tests. # Run the server-side tests.
@@ -72,7 +73,6 @@ test-server:
--compilers js:babel-core/register \ --compilers js:babel-core/register \
--require source-map-support/register \ --require source-map-support/register \
--reporter spec \ --reporter spec \
--timeout 5000 \
--fgrep "$(GREP)" \ --fgrep "$(GREP)" \
./test/server.js ./test/server.js

View File

@@ -48,7 +48,7 @@ class Content extends React.Component {
* @return {Boolean} shouldUpdate * @return {Boolean} shouldUpdate
*/ */
shouldComponentUpdate(props, state) { shouldComponentUpdate = (props, state) => {
if (props.state.isNative) return false if (props.state.isNative) return false
return ( return (
props.state.selection != this.props.state.selection || props.state.selection != this.props.state.selection ||
@@ -63,21 +63,21 @@ class Content extends React.Component {
* @param {Object} props * @param {Object} props
*/ */
componentWillMount() { componentWillMount = () => {
this.tmp.isRendering = true this.tmp.isRendering = true
} }
componentWillUpdate(props, state) { componentWillUpdate = (props, state) => {
this.tmp.isRendering = true this.tmp.isRendering = true
} }
componentDidMount() { componentDidMount = () => {
setTimeout(() => { setTimeout(() => {
this.tmp.isRendering = false this.tmp.isRendering = false
}) })
} }
componentDidUpdate(props, state) { componentDidUpdate = (props, state) => {
setTimeout(() => { setTimeout(() => {
this.tmp.isRendering = false this.tmp.isRendering = false
}) })
@@ -89,7 +89,7 @@ class Content extends React.Component {
* @param {Event} e * @param {Event} e
*/ */
onBeforeInput(e) { onBeforeInput = (e) => {
this.props.onBeforeInput(e) this.props.onBeforeInput(e)
} }
@@ -99,7 +99,7 @@ class Content extends React.Component {
* @param {Event} e * @param {Event} e
*/ */
onBlur(e) { onBlur = (e) => {
if (this.tmp.isCopying) return if (this.tmp.isCopying) return
let { state } = this.props let { state } = this.props
@@ -117,7 +117,7 @@ class Content extends React.Component {
* @param {State} state * @param {State} state
*/ */
onChange(state) { onChange = (state) => {
this.props.onChange(state) this.props.onChange(state)
} }
@@ -127,7 +127,7 @@ class Content extends React.Component {
* @param {Event} e * @param {Event} e
*/ */
onCopy(e) { onCopy = (e) => {
this.onCutCopy(e) this.onCutCopy(e)
} }
@@ -137,7 +137,7 @@ class Content extends React.Component {
* @param {Event} e * @param {Event} e
*/ */
onCut(e) { onCut = (e) => {
this.onCutCopy(e) this.onCutCopy(e)
// Once the cut has successfully executed, delete the current selection. // Once the cut has successfully executed, delete the current selection.
@@ -154,7 +154,7 @@ class Content extends React.Component {
* @param {Event} e * @param {Event} e
*/ */
onCutCopy(e) { onCutCopy = (e) => {
const native = window.getSelection() const native = window.getSelection()
if (!native.rangeCount) return if (!native.rangeCount) return
@@ -207,7 +207,7 @@ class Content extends React.Component {
* @param {Event} e * @param {Event} e
*/ */
onKeyDown(e) { onKeyDown = (e) => {
const key = keycode(e.which) const key = keycode(e.which)
if ( if (
@@ -231,7 +231,7 @@ class Content extends React.Component {
* @param {Event} e * @param {Event} e
*/ */
onPaste(e) { onPaste = (e) => {
e.preventDefault() e.preventDefault()
const data = e.clipboardData const data = e.clipboardData
const { types } = data const { types } = data
@@ -287,7 +287,7 @@ class Content extends React.Component {
* @param {Event} e * @param {Event} e
*/ */
onSelect(e) { onSelect = (e) => {
if (this.tmp.isRendering) return if (this.tmp.isRendering) return
if (this.tmp.isCopying) return if (this.tmp.isCopying) return
@@ -326,7 +326,7 @@ class Content extends React.Component {
* @return {Element} element * @return {Element} element
*/ */
render() { render = () => {
const { state } = this.props const { state } = this.props
const { document } = state const { document } = state
const children = document.nodes const children = document.nodes
@@ -343,13 +343,13 @@ class Content extends React.Component {
<div <div
contentEditable suppressContentEditableWarning contentEditable suppressContentEditableWarning
style={style} style={style}
onBeforeInput={e => this.onBeforeInput(e)} onBeforeInput={this.onBeforeInput}
onBlur={e => this.onBlur(e)} onBlur={this.onBlur}
onCopy={e => this.onCopy(e)} onCopy={this.onCopy}
onCut={e => this.onCut(e)} onCut={this.onCut}
onKeyDown={e => this.onKeyDown(e)} onKeyDown={this.onKeyDown}
onPaste={e => this.onPaste(e)} onPaste={this.onPaste}
onSelect={e => this.onSelect(e)} onSelect={this.onSelect}
> >
{children} {children}
</div> </div>
@@ -363,7 +363,7 @@ class Content extends React.Component {
* @return {Element} element * @return {Element} element
*/ */
renderNode(node) { renderNode = (node) => {
switch (node.kind) { switch (node.kind) {
case 'block': case 'block':
case 'inline': case 'inline':
@@ -380,7 +380,7 @@ class Content extends React.Component {
* @return {Element} element * @return {Element} element
*/ */
renderElement(node) { renderElement = (node) => {
const { editor, renderNode, state } = this.props const { editor, renderNode, state } = this.props
const Component = renderNode(node) const Component = renderNode(node)
const children = node.nodes const children = node.nodes
@@ -411,7 +411,7 @@ class Content extends React.Component {
* @return {Element} element * @return {Element} element
*/ */
renderVoid(element, node) { renderVoid = (element, node) => {
const { editor, state } = this.props const { editor, state } = this.props
return ( return (
<Void <Void
@@ -432,7 +432,7 @@ class Content extends React.Component {
* @return {Element} element * @return {Element} element
*/ */
renderText(node) { renderText = (node) => {
const { editor, renderMark, state } = this.props const { editor, renderMark, state } = this.props
return ( return (
<Text <Text

View File

@@ -15,12 +15,12 @@ class Editor extends React.Component {
*/ */
static propTypes = { static propTypes = {
onChange: React.PropTypes.func.isRequired,
plugins: React.PropTypes.array, plugins: React.PropTypes.array,
renderDecorations: React.PropTypes.func, renderDecorations: React.PropTypes.func,
renderMark: React.PropTypes.func, renderMark: React.PropTypes.func,
renderNode: React.PropTypes.func, renderNode: React.PropTypes.func,
state: React.PropTypes.object.isRequired, state: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired
}; };
static defaultProps = { static defaultProps = {
@@ -46,7 +46,7 @@ class Editor extends React.Component {
* @param {Object} props * @param {Object} props
*/ */
componentWillReceiveProps(props) { componentWillReceiveProps = (props) => {
this.setState({ plugins: this.resolvePlugins(props) }) this.setState({ plugins: this.resolvePlugins(props) })
this.setState({ state: this.resolveState(props.state) }) this.setState({ state: this.resolveState(props.state) })
} }
@@ -57,7 +57,7 @@ class Editor extends React.Component {
* @return {State} state * @return {State} state
*/ */
getState() { getState = () => {
return this.state.state return this.state.state
} }
@@ -67,7 +67,7 @@ class Editor extends React.Component {
* @param {State} state * @param {State} state
*/ */
onChange(state) { onChange = (state) => {
if (state == this.state.state) return if (state == this.state.state) return
for (const plugin of this.state.plugins) { for (const plugin of this.state.plugins) {
@@ -88,7 +88,7 @@ class Editor extends React.Component {
* @param {Mixed} ...args * @param {Mixed} ...args
*/ */
onEvent(name, ...args) { onEvent = (name, ...args) => {
for (const plugin of this.state.plugins) { for (const plugin of this.state.plugins) {
if (!plugin[name]) continue if (!plugin[name]) continue
const newState = plugin[name](...args, this.state.state, this) const newState = plugin[name](...args, this.state.state, this)
@@ -98,23 +98,53 @@ class Editor extends React.Component {
} }
} }
/**
* On before input.
*
* @param {Mixed} ...args
*/
onBeforeInput = (...args) => {
this.onEvent('onBeforeInput', ...args)
}
/**
* On key down.
*
* @param {Mixed} ...args
*/
onKeyDown = (...args) => {
this.onEvent('onKeyDown', ...args)
}
/**
* On paste.
*
* @param {Mixed} ...args
*/
onPaste = (...args) => {
this.onEvent('onPaste', ...args)
}
/** /**
* Render the editor. * Render the editor.
* *
* @return {Element} element * @return {Element} element
*/ */
render() { render = () => {
return ( return (
<Content <Content
editor={this} editor={this}
state={this.state.state} state={this.state.state}
onChange={state => this.onChange(state)} onChange={this.onChange}
renderMark={mark => this.renderMark(mark)} renderMark={this.renderMark}
renderNode={node => this.renderNode(node)} renderNode={this.renderNode}
onPaste={(e, paste) => this.onEvent('onPaste', e, paste)} onPaste={this.onPaste}
onBeforeInput={e => this.onEvent('onBeforeInput', e)} onBeforeInput={this.onBeforeInput}
onKeyDown={e => this.onEvent('onKeyDown', e)} onKeyDown={this.onKeyDown}
/> />
) )
} }
@@ -126,7 +156,7 @@ class Editor extends React.Component {
* @return {Element} element * @return {Element} element
*/ */
renderNode(node) { renderNode = (node) => {
for (const plugin of this.state.plugins) { for (const plugin of this.state.plugins) {
if (!plugin.renderNode) continue if (!plugin.renderNode) continue
const component = plugin.renderNode(node, this.state.state, this) const component = plugin.renderNode(node, this.state.state, this)
@@ -142,7 +172,7 @@ class Editor extends React.Component {
* @return {Object} style * @return {Object} style
*/ */
renderMark(mark) { renderMark = (mark) => {
for (const plugin of this.state.plugins) { for (const plugin of this.state.plugins) {
if (!plugin.renderMark) continue if (!plugin.renderMark) continue
const style = plugin.renderMark(mark, this.state.state, this) const style = plugin.renderMark(mark, this.state.state, this)
@@ -165,7 +195,7 @@ class Editor extends React.Component {
* @return {Array} plugins * @return {Array} plugins
*/ */
resolvePlugins(props) { resolvePlugins = (props) => {
const { onChange, plugins, ...editorPlugin } = props const { onChange, plugins, ...editorPlugin } = props
return [ return [
editorPlugin, editorPlugin,
@@ -184,7 +214,7 @@ class Editor extends React.Component {
* @return {State} state * @return {State} state
*/ */
resolveState(state) { resolveState = (state) => {
const { plugins } = this.state const { plugins } = this.state
let { document } = state let { document } = state

View File

@@ -14,11 +14,11 @@ class Leaf extends React.Component {
*/ */
static propTypes = { static propTypes = {
end: React.PropTypes.number.isRequired,
marks: React.PropTypes.object.isRequired, marks: React.PropTypes.object.isRequired,
node: React.PropTypes.object.isRequired, node: React.PropTypes.object.isRequired,
start: React.PropTypes.number.isRequired,
end: React.PropTypes.number.isRequired,
renderMark: React.PropTypes.func.isRequired, renderMark: React.PropTypes.func.isRequired,
start: React.PropTypes.number.isRequired,
state: React.PropTypes.object.isRequired, state: React.PropTypes.object.isRequired,
text: React.PropTypes.string.isRequired text: React.PropTypes.string.isRequired
}; };
@@ -122,9 +122,9 @@ class Leaf extends React.Component {
end end
}) })
const style = marks.reduce((style, mark) => { const style = marks.reduce((memo, mark) => {
return { return {
...style, ...memo,
...renderMark(mark), ...renderMark(mark),
} }
}, {}) }, {})
@@ -134,7 +134,7 @@ class Leaf extends React.Component {
data-offset-key={offsetKey} data-offset-key={offsetKey}
style={style} style={style}
> >
{text || <br/>} {text || <br />}
</span> </span>
) )
} }

View File

@@ -62,10 +62,10 @@ class Text extends React.Component {
const { characters, decorations } = node const { characters, decorations } = node
const ranges = groupByMarks(decorations || characters) const ranges = groupByMarks(decorations || characters)
return ranges.map((range, i, ranges) => { return ranges.map((range, i, original) => {
const previous = ranges.slice(0, i) const previous = original.slice(0, i)
const offset = previous.size const offset = previous.size
? previous.map(range => range.text).join('').length ? previous.map(r => r.text).join('').length
: 0 : 0
return this.renderLeaf(range, offset) return this.renderLeaf(range, offset)
}) })

View File

@@ -19,7 +19,7 @@ class Void extends React.Component {
state: React.PropTypes.object.isRequired state: React.PropTypes.object.isRequired
}; };
render() { render = () => {
const { children, node } = this.props const { children, node } = this.props
const Tag = node.kind == 'block' ? 'div' : 'span' const Tag = node.kind == 'block' ? 'div' : 'span'
const style = { const style = {
@@ -34,7 +34,7 @@ class Void extends React.Component {
) )
} }
renderSpacer() { renderSpacer = () => {
const style = { const style = {
position: 'absolute', position: 'absolute',
top: '0px', top: '0px',
@@ -49,7 +49,7 @@ class Void extends React.Component {
) )
} }
renderLeaf() { renderLeaf = () => {
const { node, state } = this.props const { node, state } = this.props
const child = node.getTextNodes().first() const child = node.getTextNodes().first()
const text = '' const text = ''
@@ -64,7 +64,8 @@ class Void extends React.Component {
return ( return (
<Leaf <Leaf
ref={el => this.leaf = el} ref={this.renderLeafRefs}
renderMark={this.renderLeafMark}
key={offsetKey} key={offsetKey}
state={state} state={state}
node={child} node={child}
@@ -72,11 +73,18 @@ class Void extends React.Component {
end={end} end={end}
text={text} text={text}
marks={marks} marks={marks}
renderMark={mark => {}}
/> />
) )
} }
renderLeafMark = (mark) => {
return {}
}
renderLeafRefs = (el) => {
this.leaf = el
}
} }
/** /**

View File

@@ -33,7 +33,7 @@ const DEFAULTS = {
* Block. * Block.
*/ */
class Block extends Record(DEFAULTS) { class Block extends new Record(DEFAULTS) {
/** /**
* Create a new `Block` with `properties`. * Create a new `Block` with `properties`.
@@ -50,7 +50,7 @@ class Block extends Record(DEFAULTS) {
properties.key = uid(4) properties.key = uid(4)
properties.data = Data.create(properties.data) properties.data = Data.create(properties.data)
properties.isVoid = !! properties.isVoid properties.isVoid = !!properties.isVoid
properties.nodes = Block.createList(properties.nodes) properties.nodes = Block.createList(properties.nodes)
return new Block(properties).normalize() return new Block(properties).normalize()

View File

@@ -26,7 +26,7 @@ const DEFAULTS = {
* Document. * Document.
*/ */
class Document extends Record(DEFAULTS) { class Document extends new Record(DEFAULTS) {
/** /**
* Create a new `Document` with `properties`. * Create a new `Document` with `properties`.

View File

@@ -33,7 +33,7 @@ const DEFAULTS = {
* Inline. * Inline.
*/ */
class Inline extends Record(DEFAULTS) { class Inline extends new Record(DEFAULTS) {
/** /**
* Create a new `Inline` with `properties`. * Create a new `Inline` with `properties`.
@@ -50,7 +50,7 @@ class Inline extends Record(DEFAULTS) {
properties.key = uid(4) properties.key = uid(4)
properties.data = Data.create(properties.data) properties.data = Data.create(properties.data)
properties.isVoid = !! properties.isVoid properties.isVoid = !!properties.isVoid
properties.nodes = Inline.createList(properties.nodes) properties.nodes = Inline.createList(properties.nodes)
let inline = new Inline(properties) let inline = new Inline(properties)

View File

@@ -15,7 +15,7 @@ const DEFAULTS = {
* Mark. * Mark.
*/ */
class Mark extends Record(DEFAULTS) { class Mark extends new Record(DEFAULTS) {
/** /**
* Create a new `Mark` with `properties`. * Create a new `Mark` with `properties`.

View File

@@ -301,8 +301,8 @@ const Node = {
getDeepestBlocks() { getDeepestBlocks() {
const texts = this.getTextNodes() const texts = this.getTextNodes()
const set = texts.reduce((set, text) => { const set = texts.reduce((memo, text) => {
return set.add(this.getClosestBlock(text)) return memo.add(this.getClosestBlock(text))
}, new OrderedSet()) }, new OrderedSet())
return set.toList() return set.toList()
@@ -500,7 +500,7 @@ const Node = {
// Otherwise, get a set of the marks for each character in the range. // Otherwise, get a set of the marks for each character in the range.
return this return this
.getCharactersAtRange(range) .getCharactersAtRange(range)
.reduce((marks, char) => marks.union(char.marks), marks) .reduce((memo, char) => memo.union(char.marks), marks)
}, },
/** /**
@@ -573,8 +573,8 @@ const Node = {
// Calculate the offset of the nodes before the highest child. // Calculate the offset of the nodes before the highest child.
const child = this.getHighestChild(key) const child = this.getHighestChild(key)
const offset = this.nodes const offset = this.nodes
.takeUntil(node => node == child) .takeUntil(n => n == child)
.reduce((offset, child) => offset + child.length, 0) .reduce((memo, n) => memo + n.length, 0)
// Recurse if need be. // Recurse if need be.
return this.hasChild(key) return this.hasChild(key)
@@ -744,7 +744,7 @@ const Node = {
hasChild(key) { hasChild(key) {
key = normalizeKey(key) key = normalizeKey(key)
return !! this.nodes.find(node => node.key == key) return !!this.nodes.find(node => node.key == key)
}, },
/** /**
@@ -756,7 +756,7 @@ const Node = {
hasDescendant(key) { hasDescendant(key) {
key = normalizeKey(key) key = normalizeKey(key)
return !! this.nodes.find((node) => { return !!this.nodes.find((node) => {
return node.kind == 'text' return node.kind == 'text'
? node.key == key ? node.key == key
: node.key == key || node.hasDescendant(key) : node.key == key || node.hasDescendant(key)
@@ -829,9 +829,9 @@ const Node = {
*/ */
mapDescendants(iterator) { mapDescendants(iterator) {
const nodes = this.nodes.map((node, i, nodes) => { const nodes = this.nodes.map((node, i, original) => {
if (node.kind != 'text') node = node.mapDescendants(iterator) if (node.kind != 'text') node = node.mapDescendants(iterator)
return iterator(node, i, nodes) return iterator(node, i, original)
}) })
return this.merge({ nodes }) return this.merge({ nodes })
@@ -850,7 +850,6 @@ const Node = {
const keys = [] const keys = []
node = node.mapDescendants((desc) => { node = node.mapDescendants((desc) => {
// That there are no duplicate keys. // That there are no duplicate keys.
if (keys.includes(desc.key)) desc = desc.set('key', uid()) if (keys.includes(desc.key)) desc = desc.set('key', uid())
keys.push(desc.key) keys.push(desc.key)
@@ -1026,7 +1025,7 @@ function isInRange(index, text, range) {
* Transforms. * Transforms.
*/ */
for (var key in Transforms) { for (const key in Transforms) {
Node[key] = Transforms[key] Node[key] = Transforms[key]
} }

View File

@@ -18,7 +18,7 @@ const DEFAULTS = {
* Selection. * Selection.
*/ */
class Selection extends Record(DEFAULTS) { class Selection extends new Record(DEFAULTS) {
/** /**
* Create a new `Selection` with `properties`. * Create a new `Selection` with `properties`.
@@ -50,8 +50,8 @@ class Selection extends Record(DEFAULTS) {
get isCollapsed() { get isCollapsed() {
return ( return (
this.anchorKey === this.focusKey && this.anchorKey == this.focusKey &&
this.anchorOffset === this.focusOffset this.anchorOffset == this.focusOffset
) )
} }

View File

@@ -1,4 +1,5 @@
import Document from './document' import Document from './document'
import Selection from './selection' import Selection from './selection'
import Transform from './transform' import Transform from './transform'
@@ -29,7 +30,7 @@ const DEFAULTS = {
* State. * State.
*/ */
class State extends Record(DEFAULTS) { class State extends new Record(DEFAULTS) {
/** /**
* Create a new `State` with `properties`. * Create a new `State` with `properties`.
@@ -465,38 +466,8 @@ class State extends Record(DEFAULTS) {
document = document.insertFragmentAtRange(selection, fragment) document = document.insertFragmentAtRange(selection, fragment)
// Determine what the selection should be after inserting. // Determine what the selection should be after inserting.
// if (texts.size == 1) {
// after = selection
// .moveToStart()
// .moveForward(fragment.length)
// }
// else if (!nextText) {
// const text = document.getTextNodes().last()
// after = selection
// .moveToStartOf(text)
// .moveForward(lastText.length)
// }
// else if (nextBlock != startBlock || lastInline || startInline) {
// const text = document.getPreviousText(nextText)
// after = selection
// .moveToStartOf(text)
// .moveForward(lastText.length)
// }
// else {
// const text = nextNextText
// ? document.getPreviousText(nextNextText)
// : document.getPreviousText(document.getTextNodes().last())
// after = selection
// .moveToStartOf(text)
// .moveForward(lastText.length)
// }
const keys = docTexts.map(text => text.key) const keys = docTexts.map(text => text.key)
const text = document.getTextNodes().findLast(text => !keys.includes(text.key)) const text = document.getTextNodes().findLast(n => !keys.includes(n.key))
after = text after = text
? selection.moveToStartOf(text).moveForward(lastText.length) ? selection.moveToStartOf(text).moveForward(lastText.length)
@@ -508,9 +479,6 @@ class State extends Record(DEFAULTS) {
return state return state
} }
/** /**
* Insert a `text` string at the current selection. * Insert a `text` string at the current selection.
* *
@@ -868,13 +836,9 @@ class State extends Record(DEFAULTS) {
document = document.wrapInlineAtRange(selection, type, data) document = document.wrapInlineAtRange(selection, type, data)
// Determine what the selection should be after wrapping. // Determine what the selection should be after wrapping.
if (selection.isCollapsed) { if (selection.isExpanded) {
selection = selection const start = document.getNextText(selection.startKey)
} const end = document.getPreviousText(selection.endKey)
else {
const startText = document.getNextText(selection.startKey)
const endText = document.getPreviousText(selection.endKey)
selection = selection.moveToRangeOf(start, end) selection = selection.moveToRangeOf(start, end)
selection = selection.normalize(document) selection = selection.normalize(document)
} }

View File

@@ -19,7 +19,7 @@ const DEFAULTS = {
* Text. * Text.
*/ */
class Text extends Record(DEFAULTS) { class Text extends new Record(DEFAULTS) {
/** /**
* Create a new `Text` with `properties`. * Create a new `Text` with `properties`.

View File

@@ -7,7 +7,7 @@ import { List, Record } from 'immutable'
* Snapshot, with a state-like shape. * Snapshot, with a state-like shape.
*/ */
const Snapshot = Record({ const Snapshot = new Record({
document: null, document: null,
selection: null, selection: null,
steps: new List() steps: new List()
@@ -17,7 +17,7 @@ const Snapshot = Record({
* Step. * Step.
*/ */
const Step = Record({ const Step = new Record({
type: null, type: null,
args: null args: null
}) })
@@ -119,7 +119,7 @@ const DEFAULT_PROPERTIES = {
* Transform. * Transform.
*/ */
class Transform extends Record(DEFAULT_PROPERTIES) { class Transform extends new Record(DEFAULT_PROPERTIES) {
/** /**
* Create a history-ready snapshot of the current state. * Create a history-ready snapshot of the current state.
@@ -183,12 +183,12 @@ class Transform extends Record(DEFAULT_PROPERTIES) {
} }
// Apply each of the steps in the transform, arriving at a new state. // Apply each of the steps in the transform, arriving at a new state.
state = steps.reduce((state, step) => this.applyStep(state, step), state) state = steps.reduce((memo, step) => this.applyStep(memo, step), state)
// Apply the "isNative" flag, which is used to allow for natively-handled // Apply the "isNative" flag, which is used to allow for natively-handled
// content changes to skip rerendering the editor for performance. // content changes to skip rerendering the editor for performance.
state = state.merge({ state = state.merge({
isNative: !! options.isNative isNative: !!options.isNative
}) })
return state return state

View File

@@ -190,7 +190,7 @@ const Transforms = {
if (!fragment.length) return node if (!fragment.length) return node
// Make sure each node in the fragment has a unique key. // Make sure each node in the fragment has a unique key.
fragment = fragment.mapDescendants(node => node.set('key', uid())) fragment = fragment.mapDescendants(child => child.set('key', uid()))
// Split the inlines if need be. // Split the inlines if need be.
if (!node.isInlineSplitAtRange(range)) { if (!node.isInlineSplitAtRange(range)) {
@@ -414,8 +414,8 @@ const Transforms = {
const { startKey } = range const { startKey } = range
const firstText = node.getDescendant(startKey) const firstText = node.getDescendant(startKey)
const secondText = node.getNextText(startKey) const secondText = node.getNextText(startKey)
const firstChild = node.getFurthestInline(firstText) || firstText let firstChild = node.getFurthestInline(firstText) || firstText
const secondChild = node.getFurthestInline(secondText) || secondText let secondChild = node.getFurthestInline(secondText) || secondText
let parent = node.getClosestBlock(firstChild) let parent = node.getClosestBlock(firstChild)
let firstChildren = parent.nodes.takeUntil(n => n == firstChild).push(firstChild) let firstChildren = parent.nodes.takeUntil(n => n == firstChild).push(firstChild)
let secondChildren = parent.nodes.skipUntil(n => n == secondChild) let secondChildren = parent.nodes.skipUntil(n => n == secondChild)
@@ -423,8 +423,8 @@ const Transforms = {
// While the parent is a block, split the block nodes. // While the parent is a block, split the block nodes.
while (parent && d < depth) { while (parent && d < depth) {
const firstChild = parent.merge({ nodes: firstChildren }) firstChild = parent.merge({ nodes: firstChildren })
const secondChild = Block.create({ secondChild = Block.create({
nodes: secondChildren, nodes: secondChildren,
type: parent.type, type: parent.type,
data: parent.data data: parent.data
@@ -615,7 +615,7 @@ const Transforms = {
// Find the closest wrapping blocks of each text node. // Find the closest wrapping blocks of each text node.
const texts = node.getBlocksAtRange(range) const texts = node.getBlocksAtRange(range)
const wrappers = texts.reduce((wrappers, text) => { const wrappers = texts.reduce((memo, text) => {
const match = node.getClosest(text, (parent) => { const match = node.getClosest(text, (parent) => {
if (parent.kind != 'block') return false if (parent.kind != 'block') return false
if (type && parent.type != type) return false if (type && parent.type != type) return false
@@ -623,8 +623,8 @@ const Transforms = {
return true return true
}) })
if (match) wrappers = wrappers.add(match) if (match) memo = memo.add(match)
return wrappers return memo
}, new Set()) }, new Set())
// Replace each of the wrappers with their child nodes. // Replace each of the wrappers with their child nodes.
@@ -669,7 +669,7 @@ const Transforms = {
// Find the closest matching inline wrappers of each text node. // Find the closest matching inline wrappers of each text node.
const texts = this.getTextNodes() const texts = this.getTextNodes()
const wrappers = texts.reduce((wrappers, text) => { const wrappers = texts.reduce((memo, text) => {
const match = node.getClosest(text, (parent) => { const match = node.getClosest(text, (parent) => {
if (parent.kind != 'inline') return false if (parent.kind != 'inline') return false
if (type && parent.type != type) return false if (type && parent.type != type) return false
@@ -677,8 +677,8 @@ const Transforms = {
return true return true
}) })
if (match) wrappers = wrappers.add(match) if (match) memo = memo.add(match)
return wrappers return memo
}, new Set()) }, new Set())
// Replace each of the wrappers with their child nodes. // Replace each of the wrappers with their child nodes.
@@ -718,19 +718,19 @@ const Transforms = {
const da = node.getDepth(a) const da = node.getDepth(a)
const db = node.getDepth(b) const db = node.getDepth(b)
if (da == db) return 0 if (da == db) return 0
if (da > db) return -1 else if (da > db) return -1
if (da < db) return 1 else return 1
}) })
// Get the lowest common siblings, relative to the highest block. // Get the lowest common siblings, relative to the highest block.
const highest = sorted.first() const highest = sorted.first()
const depth = node.getDepth(highest) const depth = node.getDepth(highest)
const siblings = blocks.reduce((siblings, block) => { const siblings = blocks.reduce((memo, block) => {
const sibling = node.getDepth(block) == depth const sibling = node.getDepth(block) == depth
? block ? block
: node.getClosest(block, (p) => node.getDepth(p) == depth) : node.getClosest(block, (p) => node.getDepth(p) == depth)
siblings = siblings.push(sibling) memo = memo.push(sibling)
return siblings return memo
}, Block.createList()) }, Block.createList())
// Wrap the siblings in a new block. // Wrap the siblings in a new block.
@@ -745,9 +745,9 @@ const Transforms = {
const last = siblings.last() const last = siblings.last()
const parent = node.getParent(highest) const parent = node.getParent(highest)
const nodes = parent.nodes const nodes = parent.nodes
.takeUntil(node => node == first) .takeUntil(n => n == first)
.push(wrapper) .push(wrapper)
.concat(parent.nodes.skipUntil(node => node == last).rest()) .concat(parent.nodes.skipUntil(n => n == last).rest())
// Update the parent. // Update the parent.
node = parent == node node = parent == node

View File

@@ -6,17 +6,25 @@ import { IS_WINDOWS, IS_MAC } from '../utils/environment'
/** /**
* Default block renderer. * Default block renderer.
*
* @param {Object} props
* @return {Element} element
*/ */
const DEFAULT_BLOCK = props => <div>{props.children}</div> function DEFAULT_BLOCK(props) {
return <div>{props.children}</div>
}
/** /**
* Default inline renderer. * Default inline renderer.
* *
* @type {Component} * @param {Object} props
* @return {Element} element
*/ */
const DEFAULT_INLINE = props => <span>{props.children}</span> function DEFAULT_INLINE(props) {
return <span>{props.children}</span>
}
/** /**
* Default mark renderer. * Default mark renderer.

View File

@@ -78,16 +78,16 @@ class Html {
let nodes = this.deserializeElements(children) let nodes = this.deserializeElements(children)
// HACK: ensure for now that all top-level inline are wrapping into a block. // HACK: ensure for now that all top-level inline are wrapping into a block.
nodes = nodes.reduce((nodes, node, i, original) => { nodes = nodes.reduce((memo, node, i, original) => {
if (node.kind == 'block') { if (node.kind == 'block') {
nodes.push(node) memo.push(node)
return nodes return memo
} }
if (i > 0 && original[i - 1].kind != 'block') { if (i > 0 && original[i - 1].kind != 'block') {
const block = nodes[nodes.length - 1] const block = memo[memo.length - 1]
block.nodes.push(node) block.nodes.push(node)
return nodes return memo
} }
const block = { const block = {
@@ -96,8 +96,8 @@ class Html {
nodes: [node] nodes: [node]
} }
nodes.push(block) memo.push(block)
return nodes return memo
}, []) }, [])
const state = Raw.deserialize({ nodes }) const state = Raw.deserialize({ nodes })
@@ -151,9 +151,7 @@ class Html {
: ret : ret
} }
return node return node || next(element.children)
? node
: next(element.children)
} }
/** /**

View File

@@ -7,7 +7,6 @@ import Mark from '../models/mark'
import State from '../models/state' import State from '../models/state'
import Text from '../models/text' import Text from '../models/text'
import groupByMarks from '../utils/group-by-marks' import groupByMarks from '../utils/group-by-marks'
import { Map } from 'immutable'
/** /**
* Serialize a `state`. * Serialize a `state`.
@@ -31,7 +30,7 @@ function serializeNode(node) {
switch (node.kind) { switch (node.kind) {
case 'document': { case 'document': {
return { return {
nodes: node.nodes.toArray().map(node => serializeNode(node)) nodes: node.nodes.toArray().map(child => serializeNode(child))
} }
} }
case 'text': { case 'text': {
@@ -45,11 +44,14 @@ function serializeNode(node) {
const obj = {} const obj = {}
obj.kind = node.kind obj.kind = node.kind
obj.type = node.type obj.type = node.type
obj.nodes = node.nodes.toArray().map(node => serializeNode(node)) obj.nodes = node.nodes.toArray().map(child => serializeNode(child))
if (node.isVoid) obj.isVoid = node.isVoid if (node.isVoid) obj.isVoid = node.isVoid
if (node.data.size) obj.data = node.data.toJSON() if (node.data.size) obj.data = node.data.toJSON()
return obj return obj
} }
default: {
throw new Error(`Unknown node kind "${node.kind}".`)
}
} }
} }
@@ -130,6 +132,9 @@ function deserializeNode(object) {
characters: deserializeRanges(object.ranges) characters: deserializeRanges(object.ranges)
}) })
} }
default: {
throw new Error(`Unknown node kind "${object.kind}".`)
}
} }
} }
@@ -145,7 +150,7 @@ function deserializeRanges(array) {
const marks = object.marks || [] const marks = object.marks || []
const chars = object.text const chars = object.text
.split('') .split('')
.map(char => { .map((char) => {
return Character.create({ return Character.create({
text: char, text: char,
marks: Mark.createSet(marks.map(deserializeMark)) marks: Mark.createSet(marks.map(deserializeMark))

View File

@@ -6,13 +6,13 @@ import Parser from 'ua-parser-js'
* Export. * Export.
*/ */
export const IS_ANDROID = process.browser && browser.name === 'android' export const IS_ANDROID = process.browser && browser.name == 'android'
export const IS_CHROME = process.browser && browser.name === 'chrome' export const IS_CHROME = process.browser && browser.name == 'chrome'
export const IS_EDGE = process.browser && browser.name === 'edge' export const IS_EDGE = process.browser && browser.name == 'edge'
export const IS_FIREFOX = process.browser && browser.name === 'firefox' export const IS_FIREFOX = process.browser && browser.name == 'firefox'
export const IS_IE = process.browser && browser.name === 'ie' export const IS_IE = process.browser && browser.name == 'ie'
export const IS_IOS = process.browser && browser.name === 'ios' export const IS_IOS = process.browser && browser.name == 'ios'
export const IS_MAC = process.browser && new Parser().getOS().name === 'Mac OS' export const IS_MAC = process.browser && new Parser().getOS().name == 'Mac OS'
export const IS_SAFARI = process.browser && browser.name === 'safari' export const IS_SAFARI = process.browser && browser.name == 'safari'
export const IS_UBUNTU = process.browser && new Parser().getOS().name === 'Ubuntu' export const IS_UBUNTU = process.browser && new Parser().getOS().name == 'Ubuntu'
export const IS_WINDOWS = process.browser && new Parser().getOS().name.includes('Windows') export const IS_WINDOWS = process.browser && new Parser().getOS().name.includes('Windows')

View File

@@ -37,7 +37,7 @@ function findKey(node) {
} }
// Shouldn't get here... else we have an edge case to handle. // Shouldn't get here... else we have an edge case to handle.
console.error('No offset key found for node:', node) throw new Error('No offset key found for node.')
} }
/** /**

View File

@@ -3,11 +3,13 @@
"version": "0.1.0", "version": "0.1.0",
"license": "MIT", "license": "MIT",
"repository": "git://github.com/ianstormtaylor/slate.git", "repository": "git://github.com/ianstormtaylor/slate.git",
"main": "./lib/index.js", "main": "./dist/index.js",
"scripts": { "scripts": {
"prepublish": "make dist",
"test": "make test" "test": "make test"
}, },
"dependencies": { "dependencies": {
"cheerio": "^0.20.0",
"detect-browser": "^1.3.3", "detect-browser": "^1.3.3",
"immutable": "^3.8.1", "immutable": "^3.8.1",
"keycode": "^2.1.2", "keycode": "^2.1.2",
@@ -22,14 +24,17 @@
"devDependencies": { "devDependencies": {
"babel-cli": "^6.10.1", "babel-cli": "^6.10.1",
"babel-core": "^6.9.1", "babel-core": "^6.9.1",
"babel-eslint": "^6.1.0",
"babel-polyfill": "^6.9.1", "babel-polyfill": "^6.9.1",
"babel-preset-es2015": "^6.9.0", "babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.5.0", "babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.5.0", "babel-preset-stage-0": "^6.5.0",
"babelify": "^7.3.0", "babelify": "^7.3.0",
"browserify": "^13.0.1", "browserify": "^13.0.1",
"cheerio": "^0.20.0",
"component-type": "^1.2.1", "component-type": "^1.2.1",
"eslint": "^3.0.1",
"eslint-plugin-import": "^1.10.2",
"eslint-plugin-react": "^5.2.2",
"exorcist": "^0.4.0", "exorcist": "^0.4.0",
"mocha": "^2.5.3", "mocha": "^2.5.3",
"mocha-phantomjs": "^4.0.2", "mocha-phantomjs": "^4.0.2",
@@ -41,11 +46,9 @@
"read-metadata": "^1.0.0", "read-metadata": "^1.0.0",
"selection-position": "^1.0.0", "selection-position": "^1.0.0",
"source-map-support": "^0.4.0", "source-map-support": "^0.4.0",
"standard": "^7.1.2",
"to-camel-case": "^1.0.0", "to-camel-case": "^1.0.0",
"to-title-case": "^1.0.0", "to-title-case": "^1.0.0",
"watchify": "^3.7.0", "watchify": "^3.7.0"
"xml2js": "^0.4.16"
}, },
"keywords": [ "keywords": [
"canvas", "canvas",