diff --git a/.eslintrc b/.eslintrc index e48b87ac4..47894b952 100644 --- a/.eslintrc +++ b/.eslintrc @@ -142,6 +142,18 @@ ], "valid-typeof": "error", "yield-star-spacing": ["error", "after"], - "yoda": ["error", "never"] + "yoda": ["error", "never"], + "padding-line-between-statements": [ + "error", + { "blankLine": "always", "prev": "multiline-expression", "next": "*" }, + { "blankLine": "any", "prev": "multiline-expression", "next": "return" }, + { "blankLine": "always", "prev": "*", "next": "multiline-expression" }, + { "blankLine": "always", "prev": "*", "next": "multiline-expression" }, + { "blankLine": "always", "prev": "multiline-block-like", "next": "*" }, + { "blankLine": "any", "prev": "multiline-block-like", "next": "return" }, + { "blankLine": "always", "prev": "*", "next": "multiline-block-like" }, + { "blankLine": "always", "prev": "*", "next": "multiline-block-like" }, + { "blankLine": "any", "prev": "case", "next": "case" } + ] } } diff --git a/examples/code-highlighting/index.js b/examples/code-highlighting/index.js index d24b50e00..380b05e22 100644 --- a/examples/code-highlighting/index.js +++ b/examples/code-highlighting/index.js @@ -137,6 +137,7 @@ class CodeHighlighting extends React.Component { renderMark = props => { const { children, mark, attributes } = props + switch (mark.type) { case 'comment': return ( diff --git a/examples/emojis/index.js b/examples/emojis/index.js index 3a386698d..f53a620d9 100644 --- a/examples/emojis/index.js +++ b/examples/emojis/index.js @@ -154,6 +154,7 @@ class Emojis extends React.Component { renderNode = props => { const { attributes, children, node, isSelected } = props + switch (node.type) { case 'paragraph': { return
{children}
diff --git a/examples/forced-layout/index.js b/examples/forced-layout/index.js index dec1027c8..b198992ad 100644 --- a/examples/forced-layout/index.js +++ b/examples/forced-layout/index.js @@ -90,6 +90,7 @@ class ForcedLayout extends React.Component { renderNode = props => { const { attributes, children, node } = props + switch (node.type) { case 'title': return{children}diff --git a/examples/paste-html/index.js b/examples/paste-html/index.js index c3d08f359..48e5b2c03 100644 --- a/examples/paste-html/index.js +++ b/examples/paste-html/index.js @@ -50,6 +50,7 @@ const RULES = [ { deserialize(el, next) { const block = BLOCK_TAGS[el.tagName.toLowerCase()] + if (block) { return { object: 'block', @@ -62,6 +63,7 @@ const RULES = [ { deserialize(el, next) { const mark = MARK_TAGS[el.tagName.toLowerCase()] + if (mark) { return { object: 'mark', @@ -202,6 +204,7 @@ class PasteHtml extends React.Component { renderNode = props => { const { attributes, children, node, isSelected } = props + switch (node.type) { case 'quote': return
{children}@@ -258,6 +261,7 @@ class PasteHtml extends React.Component { renderMark = props => { const { children, mark, attributes } = props + switch (mark.type) { case 'bold': return {children} diff --git a/examples/rich-text/index.js b/examples/rich-text/index.js index 4fdff7382..0f7a50cec 100644 --- a/examples/rich-text/index.js +++ b/examples/rich-text/index.js @@ -242,6 +242,7 @@ class RichTextExample extends React.Component { const parent = value.document.getParent(value.blocks.first().key) isActive = this.hasBlock('list-item') && parent && parent.type === type } + const onMouseDown = event => this.onClickBlock(event, type) return ( @@ -284,6 +285,7 @@ class RichTextExample extends React.Component { renderNode = props => { const { attributes, children, node } = props + switch (node.type) { case 'block-quote': return
{children}@@ -309,6 +311,7 @@ class RichTextExample extends React.Component { renderMark = props => { const { children, mark, attributes } = props + switch (mark.type) { case 'bold': return {children} diff --git a/examples/rtl/index.js b/examples/rtl/index.js index 34bd46c02..10532aebb 100644 --- a/examples/rtl/index.js +++ b/examples/rtl/index.js @@ -75,6 +75,7 @@ class RTL extends React.Component { renderNode = props => { const { attributes, children, node } = props + switch (node.type) { case 'block-quote': return
{children}diff --git a/examples/search-highlighting/index.js b/examples/search-highlighting/index.js index 2815f2d05..456e636ee 100644 --- a/examples/search-highlighting/index.js +++ b/examples/search-highlighting/index.js @@ -142,6 +142,7 @@ class SearchHighlighting extends React.Component { renderMark = props => { const { children, mark, attributes } = props + switch (mark.type) { case 'highlight': return ( diff --git a/examples/syncing-operations/index.js b/examples/syncing-operations/index.js index 611eac221..ecd60c91a 100644 --- a/examples/syncing-operations/index.js +++ b/examples/syncing-operations/index.js @@ -198,6 +198,7 @@ class SyncingEditor extends React.Component { renderMark = props => { const { children, mark, attributes } = props + switch (mark.type) { case 'bold': return {children} diff --git a/examples/tables/index.js b/examples/tables/index.js index 3a2338656..d950c1e42 100644 --- a/examples/tables/index.js +++ b/examples/tables/index.js @@ -142,6 +142,7 @@ class Tables extends React.Component { renderNode = props => { const { attributes, children, node } = props + switch (node.type) { case 'table': return ( @@ -165,6 +166,7 @@ class Tables extends React.Component { renderMark = props => { const { children, mark, attributes } = props + switch (mark.type) { case 'bold': return {children} diff --git a/package.json b/package.json index 6dc0f5878..165cfde8b 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "copy-webpack-plugin": "^4.4.1", "cross-env": "^5.1.3", "css-loader": "^0.28.9", - "eslint": "^4.16.0", + "eslint": "^4.19.1", "eslint-config-prettier": "^2.9.0", "eslint-plugin-import": "^2.8.0", "eslint-plugin-prettier": "^2.5.0", diff --git a/packages/slate-html-serializer/src/index.js b/packages/slate-html-serializer/src/index.js index 2da99649f..f4be5b56b 100644 --- a/packages/slate-html-serializer/src/index.js +++ b/packages/slate-html-serializer/src/index.js @@ -201,6 +201,7 @@ class Html { elements.filter(this.cruftNewline).forEach(element => { const node = this.deserializeElement(element) + switch (typeOf(node)) { case 'array': nodes = nodes.concat(node) diff --git a/packages/slate-html-serializer/test/serialize/block-nested.js b/packages/slate-html-serializer/test/serialize/block-nested.js index 9fe07ff25..bb3b07d5e 100644 --- a/packages/slate-html-serializer/test/serialize/block-nested.js +++ b/packages/slate-html-serializer/test/serialize/block-nested.js @@ -7,6 +7,7 @@ export const rules = [ { serialize(obj, children) { if (obj.object != 'block') return + switch (obj.type) { case 'paragraph': return React.createElement('p', {}, children) diff --git a/packages/slate-hyperscript/src/index.js b/packages/slate-hyperscript/src/index.js index 29b9a3a7f..3423efa9a 100644 --- a/packages/slate-hyperscript/src/index.js +++ b/packages/slate-hyperscript/src/index.js @@ -106,6 +106,7 @@ const CREATORS = { } const nodes = createChildren(children, { key: attributes.key }) + nodes[0].__decorations = (nodes[0].__decorations || []).concat([ { anchorOffset: 0, @@ -159,6 +160,7 @@ const CREATORS = { }) ) ) + // store or combine partial decorations (keyed with anchor / focus) text.__decorations .filter(d => d._key !== undefined) @@ -169,9 +171,11 @@ const CREATORS = { partial.withKey(text.key) ) ) + delete partialDecorations[partial._key] return } + partialDecorations[partial._key] = partial.withKey(text.key) }) } @@ -287,6 +291,7 @@ function createChildren(children, options = {}) { children.forEach((child, index) => { const isLast = index === children.length - 1 + // If the child is a non-text node, push the current node and the new child // onto the array, then creating a new node for future selection tracking. if (Node.isNode(child) && !Text.isText(child)) { @@ -298,10 +303,13 @@ function createChildren(children, options = {}) { ) { array.push(node) } + array.push(child) + node = isLast ? null : Text.create({ leaves: [{ text: '', marks: options.marks }] }) + length = 0 } @@ -331,6 +339,7 @@ function createChildren(children, options = {}) { if (__anchor != null) node.__anchor = __anchor + length if (__focus != null) node.__focus = __focus + length + if (__decorations != null) { node.__decorations = (node.__decorations || []).concat( __decorations.map( diff --git a/packages/slate-react/src/components/content.js b/packages/slate-react/src/components/content.js index d6731bd64..a250cebe3 100644 --- a/packages/slate-react/src/components/content.js +++ b/packages/slate-react/src/components/content.js @@ -381,6 +381,7 @@ class Content extends React.Component { change.splitBlockAtRange(range) } }) + break } diff --git a/packages/slate-react/src/components/node.js b/packages/slate-react/src/components/node.js index 6bbf37e3f..8da0f04ce 100644 --- a/packages/slate-react/src/components/node.js +++ b/packages/slate-react/src/components/node.js @@ -135,6 +135,7 @@ class Node extends React.Component { const childrenDecorations = getChildrenDecorations(node, decs) let children = [] + node.nodes.forEach((child, i) => { const isChildSelected = !!indexes && indexes.start <= i && i < indexes.end @@ -169,6 +170,7 @@ class Node extends React.Component { placeholder = React.cloneElement(placeholder, { key: `${node.key}-placeholder`, }) + children = [placeholder, ...children] } diff --git a/packages/slate-react/src/plugins/after.js b/packages/slate-react/src/plugins/after.js index 2bb32c148..3bc6f4981 100644 --- a/packages/slate-react/src/plugins/after.js +++ b/packages/slate-react/src/plugins/after.js @@ -438,6 +438,7 @@ function AfterPlugin() { const { document, isInVoid, previousText, startText } = value const isPreviousInVoid = previousText && document.hasVoidParent(previousText.key) + if (isInVoid || isPreviousInVoid || startText.text == '') { event.preventDefault() return change.collapseCharBackward() @@ -447,6 +448,7 @@ function AfterPlugin() { if (Hotkeys.isCollapseCharForward(event)) { const { document, isInVoid, nextText, startText } = value const isNextInVoid = nextText && document.hasVoidParent(nextText.key) + if (isInVoid || isNextInVoid || startText.text == '') { event.preventDefault() return change.collapseCharForward() @@ -457,6 +459,7 @@ function AfterPlugin() { const { document, isInVoid, previousText, startText } = value const isPreviousInVoid = previousText && document.hasVoidParent(previousText.key) + if (isInVoid || isPreviousInVoid || startText.text == '') { event.preventDefault() return change.extendCharBackward() @@ -466,6 +469,7 @@ function AfterPlugin() { if (Hotkeys.isExtendCharForward(event)) { const { document, isInVoid, nextText, startText } = value const isNextInVoid = nextText && document.hasVoidParent(nextText.key) + if (isInVoid || isNextInVoid || startText.text == '') { event.preventDefault() return change.extendCharForward() diff --git a/packages/slate-react/src/utils/clone-fragment.js b/packages/slate-react/src/utils/clone-fragment.js index 56ce6cee5..6777ee53a 100644 --- a/packages/slate-react/src/utils/clone-fragment.js +++ b/packages/slate-react/src/utils/clone-fragment.js @@ -59,6 +59,7 @@ function cloneFragment(event, value, fragment = value.fragment) { // Remove any zero-width space spans from the cloned DOM so that they don't // show up elsewhere when pasted. + // eslint-disable-next-line padding-line-between-statements ;[].slice.call(contents.querySelectorAll(ZERO_WIDTH_SELECTOR)).forEach(zw => { const isNewline = zw.getAttribute(ZERO_WIDTH_ATTRIBUTE) === 'n' zw.textContent = isNewline ? '\n' : '' diff --git a/packages/slate-react/src/utils/get-children-decorations.js b/packages/slate-react/src/utils/get-children-decorations.js index 9426d3da3..bb3ac48ad 100644 --- a/packages/slate-react/src/utils/get-children-decorations.js +++ b/packages/slate-react/src/utils/get-children-decorations.js @@ -62,6 +62,7 @@ function orderChildDecorations(node, decorations) { // Map each key to its global order const keyOrders = { [node.key]: 0 } let globalOrder = 1 + node.forEachDescendant(child => { keyOrders[child.key] = globalOrder globalOrder = globalOrder + 1 @@ -84,6 +85,7 @@ function orderChildDecorations(node, decorations) { startKeyOrder === undefined ? 0 : getContainingChildOrder(childNodes, keyOrders, startKeyOrder) + endPoints.push({ isRangeStart: true, order: containingChildOrder - 0.5, @@ -92,6 +94,7 @@ function orderChildDecorations(node, decorations) { // Range end. const endKeyOrder = (keyOrders[decoration.endKey] || globalOrder) + 0.5 + endPoints.push({ isRangeEnd: true, order: endKeyOrder, diff --git a/packages/slate-react/src/utils/remove-all-ranges.js b/packages/slate-react/src/utils/remove-all-ranges.js index 7807b520f..045b9f2b0 100644 --- a/packages/slate-react/src/utils/remove-all-ranges.js +++ b/packages/slate-react/src/utils/remove-all-ranges.js @@ -8,6 +8,7 @@ function removeAllRanges(selection) { const doc = window.document + if (doc && doc.body.createTextRange) { // All IE but Edge const range = doc.body.createTextRange() diff --git a/packages/slate-react/src/utils/scroll-to-selection.js b/packages/slate-react/src/utils/scroll-to-selection.js index d05cc94af..5b6cae2bb 100644 --- a/packages/slate-react/src/utils/scroll-to-selection.js +++ b/packages/slate-react/src/utils/scroll-to-selection.js @@ -133,10 +133,13 @@ function scrollToSelection(selection) { height = offsetHeight scrollerTop = scrollerRect.top + parseInt(borderTopWidth, 10) scrollerLeft = scrollerRect.left + parseInt(borderLeftWidth, 10) + scrollerBordersY = parseInt(borderTopWidth, 10) + parseInt(borderBottomWidth, 10) + scrollerBordersX = parseInt(borderLeftWidth, 10) + parseInt(borderRightWidth, 10) + scrollerPaddingTop = parseInt(paddingTop, 10) scrollerPaddingBottom = parseInt(paddingBottom, 10) scrollerPaddingLeft = parseInt(paddingLeft, 10) diff --git a/packages/slate/src/changes/at-current-range.js b/packages/slate/src/changes/at-current-range.js index 6a996d23a..bee3fc7f8 100644 --- a/packages/slate/src/changes/at-current-range.js +++ b/packages/slate/src/changes/at-current-range.js @@ -40,6 +40,7 @@ PROXY_TRANSFORMS.forEach(method => { const { selection } = value const methodAtRange = `${method}AtRange` change[methodAtRange](selection, ...args) + if (method.match(/Backward$/)) { change.collapseToStart() } else if (method.match(/Forward$/)) { @@ -53,6 +54,7 @@ Changes.setBlock = (...args) => { 'slate@0.33.0', 'The `setBlock` method of Slate changes has been renamed to `setBlocks`.' ) + Changes.setBlocks(...args) } @@ -61,6 +63,7 @@ Changes.setInline = (...args) => { 'slate@0.33.0', 'The `setInline` method of Slate changes has been renamed to `setInlines`.' ) + Changes.setInlines(...args) } @@ -230,6 +233,7 @@ Changes.splitBlock = (change, depth = 1) => { const { selection, document } = value const marks = selection.marks || document.getInsertMarksAtRange(selection) change.splitBlockAtRange(selection, depth).collapseToEnd() + if (marks && marks.size !== 0) { change.select({ marks }) } diff --git a/packages/slate/src/changes/at-range.js b/packages/slate/src/changes/at-range.js index 1255cfaee..bad772efc 100644 --- a/packages/slate/src/changes/at-range.js +++ b/packages/slate/src/changes/at-range.js @@ -370,6 +370,7 @@ Changes.deleteBackwardAtRange = (change, range, n = 1, options = {}) => { // If the range is at the start of the text node, we need to figure out what // is behind it to know how to delete... const text = document.getDescendant(startKey) + if (range.isAtStartOf(text)) { const prev = document.getPreviousText(text.key) const prevBlock = document.getClosestBlock(prev.key) @@ -414,6 +415,7 @@ Changes.deleteBackwardAtRange = (change, range, n = 1, options = {}) => { while (n > traversed) { node = document.getPreviousText(node.key) const next = traversed + node.text.length + if (n <= next) { offset = next - n break @@ -529,6 +531,7 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => { if (block && block.isEmpty && document.nodes.size !== 1) { const nextBlock = document.getNextBlock(block.key) change.removeNodeByKey(block.key, { normalize }) + if (nextBlock && nextBlock.key) { change.moveToStartOf(nextBlock) } @@ -543,6 +546,7 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => { // If the range is at the start of the text node, we need to figure out what // is behind it to know how to delete... const text = document.getDescendant(startKey) + if (range.isAtEndOf(text)) { const next = document.getNextText(text.key) const nextBlock = document.getClosestBlock(next.key) @@ -587,6 +591,7 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => { while (n > traversed) { node = document.getNextText(node.key) const next = traversed + node.text.length + if (n <= next) { offset = n - traversed break @@ -649,6 +654,7 @@ Changes.insertBlockAtRange = (change, range, block, options = {}) => { change.splitDescendantsByKey(startBlock.key, startKey, startOffset, { normalize: false, }) + change.insertNodeByKey(parent.key, index + 1, block, { normalize }) } @@ -673,6 +679,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => { // If the range is expanded, delete it first. if (range.isExpanded) { change.deleteAtRange(range, { normalize: false }) + if (change.value.document.getDescendant(range.startKey)) { range = range.collapseToStart() } else { @@ -764,6 +771,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => { nextNodes.forEach((node, i) => { const newIndex = lastIndex + i + change.moveNodeByKey(node.key, lastBlock.key, newIndex, { normalize: false, }) @@ -784,6 +792,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => { firstBlock.nodes.forEach((inline, i) => { const o = startOffset == 0 ? 0 : 1 const newIndex = inlineIndex + i + o + change.insertNodeByKey(startBlock.key, newIndex, inline, { normalize: false, }) @@ -868,6 +877,7 @@ Changes.insertTextAtRange = (change, range, text, marks, options = {}) => { if (normalize === undefined) { normalize = range.isExpanded && marks.size !== 0 } + change.insertTextByKey(key, offset, text, marks, { normalize: false }) if (normalize) { @@ -963,6 +973,7 @@ Changes.setBlockAtRange = (...args) => { 'slate@0.33.0', 'The `setBlockAtRange` method of Slate changes has been renamed to `setBlocksAtRange`.' ) + Changes.setBlocksAtRange(...args) } @@ -992,6 +1003,7 @@ Changes.setInlineAtRange = (...args) => { 'slate@0.33.0', 'The `setInlineAtRange` method of Slate changes has been renamed to `setInlinesAtRange`.' ) + Changes.setInlinesAtRange(...args) } @@ -1029,9 +1041,11 @@ Changes.splitBlockAtRange = (change, range, height = 1, options = {}) => { if (range.isBackward) range = range.flip() const nextBlock = change.value.document.getNextBlock(node.key) range = range.moveAnchorToStartOf(nextBlock) + if (startKey === endKey) { range = range.moveFocusTo(range.anchorKey, endOffset - startOffset) } + change.deleteAtRange(range, { normalize }) } } @@ -1176,9 +1190,11 @@ Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => { }) } else { const firstText = firstMatch.getFirstText() + change.splitDescendantsByKey(block.key, firstText.key, 0, { normalize: false, }) + document = change.value.document children.forEach((child, i) => { @@ -1340,6 +1356,7 @@ Changes.wrapInlineAtRange = (change, range, inline, options = {}) => { if (range.isCollapsed) { // Wrapping an inline void const inlineParent = document.getClosestInline(startKey) + if (!inlineParent.isVoid) { return } @@ -1359,6 +1376,7 @@ Changes.wrapInlineAtRange = (change, range, inline, options = {}) => { change.splitDescendantsByKey(endChild.key, endKey, endOffset, { normalize: false, }) + change.splitDescendantsByKey(startChild.key, startKey, startOffset, { normalize: false, }) @@ -1407,6 +1425,7 @@ Changes.wrapInlineAtRange = (change, range, inline, options = {}) => { change.insertNodeByKey(startBlock.key, startIndex + 1, startNode, { normalize: false, }) + change.insertNodeByKey(endBlock.key, endIndex, endNode, { normalize: false, }) diff --git a/packages/slate/src/changes/by-key.js b/packages/slate/src/changes/by-key.js index 4a2cee528..e72a60bb6 100644 --- a/packages/slate/src/changes/by-key.js +++ b/packages/slate/src/changes/by-key.js @@ -387,9 +387,11 @@ Changes.replaceTextByKey = ( ) => { const { document } = change.value const textNode = document.getDescendant(key) + if (length + offset > textNode.text.length) { length = textNode.text.length - offset } + const range = Range.create({ anchorKey: key, focusKey: key, @@ -399,6 +401,7 @@ Changes.replaceTextByKey = ( let activeMarks = document.getActiveMarksAtRange(range) change.removeTextByKey(key, offset, length, { normalize: false }) + if (!marks) { // Do not use mark at index when marks and activeMarks are both empty marks = activeMarks ? activeMarks : [] @@ -407,8 +410,10 @@ Changes.replaceTextByKey = ( activeMarks = activeMarks.filter( activeMark => !marks.find(m => activeMark.type === m.type) ) + marks = activeMarks.merge(marks) } + change.insertTextByKey(key, offset, text, marks, options) } @@ -490,6 +495,7 @@ Changes.replaceNodeByKey = (change, key, newNode, options = {}) => { const index = parent.nodes.indexOf(node) change.removeNodeByKey(key, { normalize: false }) change.insertNodeByKey(parent.key, index, newNode, { normalize: false }) + if (normalize) { change.normalizeNodeByKey(parent.key) } @@ -644,6 +650,7 @@ Changes.splitDescendantsByKey = ( const prevIndex = index == null ? null : index index = previous ? node.nodes.indexOf(previous) + 1 : textOffset previous = node + change.splitNodeByKey(node.key, index, { normalize: false, target: prevIndex, @@ -727,6 +734,7 @@ Changes.unwrapNodeByKey = (change, key, options = {}) => { change.moveNodeByKey(key, parentParent.key, parentIndex, { normalize: false, }) + change.removeNodeByKey(parent.key, options) } else if (isFirst) { // Just move the node before its parent. diff --git a/packages/slate/src/changes/with-schema.js b/packages/slate/src/changes/with-schema.js index 91aeca0ef..a9f0583b8 100644 --- a/packages/slate/src/changes/with-schema.js +++ b/packages/slate/src/changes/with-schema.js @@ -69,9 +69,11 @@ function normalizeNodeAndChildren(change, node, schema) { let child = node.getFirstInvalidDescendant(schema) let path = change.value.document.getPath(node.key) + while (node && child) { normalizeNodeAndChildren(change, child, schema) node = change.value.document.refindNode(path, node.key) + if (!node) { path = [] child = null diff --git a/packages/slate/src/constants/core-schema-rules.js b/packages/slate/src/constants/core-schema-rules.js index 285deca0d..7709c827d 100644 --- a/packages/slate/src/constants/core-schema-rules.js +++ b/packages/slate/src/constants/core-schema-rules.js @@ -167,6 +167,7 @@ const CORE_SCHEMA_RULES = [ change.insertNodeByKey(node.key, shift + index, Text.create(), { normalize: false, }) + shift++ } @@ -174,6 +175,7 @@ const CORE_SCHEMA_RULES = [ change.insertNodeByKey(node.key, shift + index + 1, Text.create(), { normalize: false, }) + shift++ } }) diff --git a/packages/slate/src/models/change.js b/packages/slate/src/models/change.js index 9f6fae2bd..a7404de9d 100644 --- a/packages/slate/src/models/change.js +++ b/packages/slate/src/models/change.js @@ -44,6 +44,7 @@ class Change { const { value } = attrs this.value = value this.operations = new List() + this.flags = { normalize: true, ...pick(attrs, ['merge', 'save', 'normalize']), @@ -152,6 +153,7 @@ class Change { withoutNormalization(customChange) { const original = this.flags.normalize this.setOperationFlag('normalize', false) + try { customChange(this) // if the change function worked then run normalization diff --git a/packages/slate/src/models/leaf.js b/packages/slate/src/models/leaf.js index 40ee50f11..e6e371da7 100644 --- a/packages/slate/src/models/leaf.js +++ b/packages/slate/src/models/leaf.js @@ -249,6 +249,7 @@ class Leaf extends Record(DEFAULTS) { 'slate@0.34.0', 'The `characters` property of Slate objects is deprecated' ) + const { marks } = this const characters = Character.createList( this.text.split('').map(char => { diff --git a/packages/slate/src/models/node.js b/packages/slate/src/models/node.js index bbfc29ac7..ed1213f53 100644 --- a/packages/slate/src/models/node.js +++ b/packages/slate/src/models/node.js @@ -43,6 +43,7 @@ class Node { 'slate@0.32.0', 'The `kind` property of Slate objects has been renamed to `object`.' ) + object = attrs.kind } @@ -55,6 +56,7 @@ class Node { return Inline.create(attrs) case 'text': return Text.create(attrs) + default: { throw new Error('`Node.create` requires a `object` string.') } @@ -132,6 +134,7 @@ class Node { 'slate@0.32.0', 'The `kind` property of Slate objects has been renamed to `object`.' ) + object = value.kind } @@ -144,6 +147,7 @@ class Node { return Inline.fromJSON(value) case 'text': return Text.fromJSON(value) + default: { throw new Error( `\`Node.fromJSON\` requires an \`object\` of either 'block', 'document', 'inline' or 'text', but you passed: ${value}` @@ -350,6 +354,7 @@ class Node { if (this.hasChild(key)) return List([this]) let ancestors + this.nodes.find(node => { if (node.object == 'text') return false ancestors = node.getAncestors(key) @@ -480,6 +485,7 @@ class Node { range = range.normalize(this) if (range.isUnset) return List() const { startKey, endKey, startOffset, endOffset } = range + if (startKey === endKey) { const endText = this.getDescendant(endKey) return endText.characters.slice(startOffset, endOffset) @@ -489,6 +495,7 @@ class Node { if (t.key === startKey) { return t.characters.slice(startOffset) } + if (t.key === endKey) { return t.characters.slice(0, endOffset) } @@ -519,6 +526,7 @@ class Node { getClosest(key, iterator) { key = assertKey(key) const ancestors = this.getAncestors(key) + if (!ancestors) { throw new Error(`Could not find a descendant node with key "${key}".`) } @@ -760,6 +768,7 @@ class Node { getFurthest(key, iterator) { const ancestors = this.getAncestors(key) + if (!ancestors) { key = assertKey(key) throw new Error(`Could not find a descendant node with key "${key}".`) @@ -856,6 +865,7 @@ class Node { this.nodes.forEach(child => { if (child.object == 'text') return + if (child.isLeafInline()) { array.push(child) } else { @@ -1004,6 +1014,7 @@ class Node { // PERF: use only one concat rather than multiple concat // becuase one concat is faster const result = [] + this.nodes.forEach(node => { result.push(node.getMarksAsArray()) }) @@ -1031,6 +1042,7 @@ class Node { getInsertMarksAtRange(range) { range = range.normalize(this) if (range.isUnset) return Set() + if (range.isCollapsed) { // PERF: range is not cachable, use key and offset as proxies for cache return this.getMarksAtPosition(range.startKey, range.startOffset) @@ -1051,6 +1063,7 @@ class Node { getOrderedMarksAtRange(range) { range = range.normalize(this) if (range.isUnset) return OrderedSet() + if (range.isCollapsed) { // PERF: range is not cachable, use key and offset as proxies for cache return this.getMarksAtPosition(range.startKey, range.startOffset) @@ -1109,6 +1122,7 @@ class Node { getActiveMarksAtRange(range) { range = range.normalize(this) if (range.isUnset) return Set() + if (range.isCollapsed) { const { startKey, startOffset } = range return this.getMarksAtPosition(startKey, startOffset).toSet() @@ -1147,11 +1161,13 @@ class Node { if (marks.size === 0) return marks let text = this.getNextText(startKey) + while (text.key !== endKey) { if (text.text.length !== 0) { marks = marks.intersect(text.getActiveMarks()) if (marks.size === 0) return Set() } + text = this.getNextText(text.key) } return marks @@ -1402,6 +1418,7 @@ class Node { refindPath(path, key) { const node = this.getDescendantAtPath(path) + if (node && node.key === key) { return path } @@ -1420,6 +1437,7 @@ class Node { refindNode(path, key) { const node = this.getDescendantAtPath(path) + if (node && node.key === key) { return node } @@ -2041,6 +2059,7 @@ class Node { getFirstInvalidDescendant(schema) { let result = null + this.nodes.find(n => { result = n.validate(schema) ? n : n.getFirstInvalidDescendant(schema) return result diff --git a/packages/slate/src/models/range.js b/packages/slate/src/models/range.js index e6a6dd726..090c4878c 100644 --- a/packages/slate/src/models/range.js +++ b/packages/slate/src/models/range.js @@ -687,6 +687,7 @@ class Range extends Record(DEFAULTS) { const anchorOffsetType = typeof anchorOffset const focusOffsetType = typeof focusOffset + if (anchorOffsetType != 'number' || focusOffsetType != 'number') { logger.warn( `The range offsets should be numbers, but they were of type "${anchorOffsetType}" and "${focusOffsetType}".` @@ -714,6 +715,7 @@ class Range extends Record(DEFAULTS) { 'The range was invalid and was reset. The range in question was:', range ) + const first = node.getFirstText() return range.merge({ anchorKey: first ? first.key : null, @@ -730,6 +732,7 @@ class Range extends Record(DEFAULTS) { 'The range anchor was set to a Node that is not a Text node. This should not happen and can degrade performance. The node in question was:', anchorNode ) + const anchorText = anchorNode.getTextAtOffset(anchorOffset) const offset = anchorNode.getOffset(anchorText.key) anchorOffset = anchorOffset - offset @@ -742,6 +745,7 @@ class Range extends Record(DEFAULTS) { 'The range focus was set to a Node that is not a Text node. This should not happen and can degrade performance. The node in question was:', focusNode ) + const focusText = focusNode.getTextAtOffset(focusOffset) const offset = focusNode.getOffset(focusText.key) focusOffset = focusOffset - offset diff --git a/packages/slate/src/models/schema.js b/packages/slate/src/models/schema.js index 0792b1ed9..2fd7913b9 100644 --- a/packages/slate/src/models/schema.js +++ b/packages/slate/src/models/schema.js @@ -369,6 +369,7 @@ class Schema extends Record(DEFAULTS) { if (max != null && offset == max) nextDef() return !!child } + function rewind() { offset -= 1 index -= 1 diff --git a/packages/slate/src/models/text.js b/packages/slate/src/models/text.js index 4a9c077a2..aa929d7f0 100644 --- a/packages/slate/src/models/text.js +++ b/packages/slate/src/models/text.js @@ -754,6 +754,7 @@ class Text extends Record(DEFAULTS) { if (result.size === 1) { const first = result.first() + if (!first.marks || first.marks.size === 0) { if (first.text === '') { return this.set('leaves', List()) diff --git a/packages/slate/src/models/value.js b/packages/slate/src/models/value.js index 5c475ff7c..b5af7a2ad 100644 --- a/packages/slate/src/models/value.js +++ b/packages/slate/src/models/value.js @@ -665,12 +665,15 @@ class Value extends Record(DEFAULTS) { if (options.preserveSelection && !options.preserveKeys) { const { document, selection } = this + object.selection.anchorPath = selection.isSet ? document.getPath(selection.anchorKey) : null + object.selection.focusPath = selection.isSet ? document.getPath(selection.focusKey) : null + delete object.selection.anchorKey delete object.selection.focusKey } @@ -681,6 +684,7 @@ class Value extends Record(DEFAULTS) { !options.preserveKeys ) { const { document } = this + object.decorations = object.decorations.map(decoration => { const withPath = { ...decoration, diff --git a/packages/slate/src/operations/apply.js b/packages/slate/src/operations/apply.js index 620570c05..f20d14bb1 100644 --- a/packages/slate/src/operations/apply.js +++ b/packages/slate/src/operations/apply.js @@ -26,6 +26,7 @@ function applyRangeAdjustments(value, checkAffected, adjustRange) { if (value.selection && checkAffected(value.selection)) { value = value.set('selection', adjustRange(value.selection)) } + if (!value.decorations) return value // check all ranges, apply adjustment if affected @@ -312,11 +313,13 @@ const APPLIERS = { ? range.moveStartTo(prev.key, prev.text.length) : next ? range.moveStartTo(next.key, 0) : range.deselect() } + if (node.hasNode(endKey)) { range = prev ? range.moveEndTo(prev.key, prev.text.length) : next ? range.moveEndTo(next.key, 0) : range.deselect() } + // If the range wasn't deselected, normalize it. if (range.isSet) return range.normalize(document) return range @@ -489,12 +492,15 @@ const APPLIERS = { // Split the node by its parent. parent = parent.splitNode(index, position) + if (properties) { const splitNode = parent.nodes.get(index + 1) + if (splitNode.object !== 'text') { parent = parent.updateNode(splitNode.merge(properties)) } } + document = document.updateNode(parent) const next = document.getNextText(node.key) diff --git a/packages/slate/src/operations/invert.js b/packages/slate/src/operations/invert.js index 40245e810..50107923c 100644 --- a/packages/slate/src/operations/invert.js +++ b/packages/slate/src/operations/invert.js @@ -201,6 +201,7 @@ function invertOperation(op) { inverseProps.anchorKey === null ? null : document.getPath(inverseProps.anchorKey) + delete inverseProps.anchorKey } @@ -209,6 +210,7 @@ function invertOperation(op) { inverseProps.focusKey === null ? null : document.getPath(inverseProps.focusKey) + delete inverseProps.focusKey } diff --git a/packages/slate/src/utils/memoize.js b/packages/slate/src/utils/memoize.js index e760d5dba..2e43e11d0 100644 --- a/packages/slate/src/utils/memoize.js +++ b/packages/slate/src/utils/memoize.js @@ -70,6 +70,7 @@ function memoize(object, properties) { if (!this.__cache) { this.__cache = new Map() // eslint-disable-line no-undef,no-restricted-globals } + if (!this.__cache_no_args) { this.__cache_no_args = {} } diff --git a/packages/slate/test/changes/at-current-range/add-marks/with-mark-object.js b/packages/slate/test/changes/at-current-range/add-marks/with-mark-object.js index 4c562f3a3..e74dbd60a 100644 --- a/packages/slate/test/changes/at-current-range/add-marks/with-mark-object.js +++ b/packages/slate/test/changes/at-current-range/add-marks/with-mark-object.js @@ -13,6 +13,7 @@ export default function(change) { data: { thing: 'value' }, }) ) + marks.push( Mark.create({ type: 'italic', diff --git a/packages/slate/test/changes/at-current-range/add-marks/with-plain-object.js b/packages/slate/test/changes/at-current-range/add-marks/with-plain-object.js index a759e2b2c..acbdbf5fd 100644 --- a/packages/slate/test/changes/at-current-range/add-marks/with-plain-object.js +++ b/packages/slate/test/changes/at-current-range/add-marks/with-plain-object.js @@ -9,6 +9,7 @@ export default function(change) { type: 'bold', data: { thing: 'value' }, }) + marks.push({ type: 'italic', data: { thing2: 'value2' }, diff --git a/packages/slate/test/changes/at-current-range/insert-fragment/hanging-selection-single-block.js b/packages/slate/test/changes/at-current-range/insert-fragment/hanging-selection-single-block.js index 66e8df7dd..7c677c776 100644 --- a/packages/slate/test/changes/at-current-range/insert-fragment/hanging-selection-single-block.js +++ b/packages/slate/test/changes/at-current-range/insert-fragment/hanging-selection-single-block.js @@ -9,6 +9,7 @@ const fragment = (