mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-20 06:01:24 +02:00
Merge branch 'pr392' into schema-normalize
This commit is contained in:
@@ -53,6 +53,10 @@ class Block extends new Record(DEFAULTS) {
|
|||||||
properties.isVoid = !!properties.isVoid
|
properties.isVoid = !!properties.isVoid
|
||||||
properties.nodes = Block.createList(properties.nodes)
|
properties.nodes = Block.createList(properties.nodes)
|
||||||
|
|
||||||
|
// if (properties.nodes.size == 0) {
|
||||||
|
// properties.nodes = properties.nodes.push(Text.create())
|
||||||
|
// }
|
||||||
|
|
||||||
return new Block(properties).normalize()
|
return new Block(properties).normalize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -51,8 +51,13 @@ class Transform {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
apply(options = {}) {
|
apply(options = {}) {
|
||||||
|
let transform = this
|
||||||
|
|
||||||
|
// Ensure that the state is normalized.
|
||||||
|
transform = transform.normalize()
|
||||||
|
|
||||||
let { merge, save, isNative = false } = options
|
let { merge, save, isNative = false } = options
|
||||||
let { state, operations } = this
|
let { state, operations } = transform
|
||||||
let { history } = state
|
let { history } = state
|
||||||
let { undos, redos } = history
|
let { undos, redos } = history
|
||||||
const previous = undos.peek()
|
const previous = undos.peek()
|
||||||
|
@@ -94,7 +94,7 @@ export function deleteAtRange(transform, range) {
|
|||||||
|
|
||||||
const lonely = document.getFurthest(endBlock, p => p.nodes.size == 1) || endBlock
|
const lonely = document.getFurthest(endBlock, p => p.nodes.size == 1) || endBlock
|
||||||
transform.removeNodeByKey(lonely.key)
|
transform.removeNodeByKey(lonely.key)
|
||||||
transform.normalizeDocument()
|
transform.normalize()
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ export function insertBlockAtRange(transform, range, block) {
|
|||||||
transform.insertNodeByKey(parent.key, index + 1, block)
|
transform.insertNodeByKey(parent.key, index + 1, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
transform.normalizeDocument()
|
transform.normalize()
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,7 +352,7 @@ export function insertFragmentAtRange(transform, range, fragment) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
transform.normalizeDocument()
|
transform.normalize()
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +386,7 @@ export function insertInlineAtRange(transform, range, inline) {
|
|||||||
|
|
||||||
transform.splitNodeByKey(startKey, startOffset)
|
transform.splitNodeByKey(startKey, startOffset)
|
||||||
transform.insertNodeByKey(parent.key, index + 1, inline)
|
transform.insertNodeByKey(parent.key, index + 1, inline)
|
||||||
transform.normalizeDocument()
|
transform.normalize()
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,7 +523,7 @@ export function splitBlockAtRange(transform, range, height = 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transform.splitNodeByKey(node.key, offset)
|
transform.splitNodeByKey(node.key, offset)
|
||||||
transform.normalizeDocument()
|
transform.normalize()
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,7 +672,7 @@ export function unwrapBlockAtRange(transform, range, properties) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
transform.normalizeDocument()
|
transform.normalize()
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,7 +714,7 @@ export function unwrapInlineAtRange(transform, range, properties) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
transform.normalizeDocument()
|
transform.normalize()
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -879,7 +879,7 @@ export function wrapInlineAtRange(transform, range, inline) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
transform.normalizeDocument()
|
transform.normalize()
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -143,6 +143,7 @@ import {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
normalize,
|
||||||
normalizeDocument,
|
normalizeDocument,
|
||||||
normalizeSelection,
|
normalizeSelection,
|
||||||
} from './normalize'
|
} from './normalize'
|
||||||
@@ -284,6 +285,7 @@ export default {
|
|||||||
* Normalize.
|
* Normalize.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
normalize,
|
||||||
normalizeDocument,
|
normalizeDocument,
|
||||||
normalizeSelection,
|
normalizeSelection,
|
||||||
|
|
||||||
|
@@ -1,4 +1,148 @@
|
|||||||
|
|
||||||
|
import Schema from '../models/schema'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only allow block nodes in documents.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DOCUMENT_CHILDREN_RULE = {
|
||||||
|
match: (node) => {
|
||||||
|
return node.kind == 'document'
|
||||||
|
},
|
||||||
|
validate: (document) => {
|
||||||
|
const { nodes } = document
|
||||||
|
const invalids = nodes.filter(n => n.kind != 'block')
|
||||||
|
return invalids.size ? invalids : null
|
||||||
|
},
|
||||||
|
normalize: (transform, document, invalids) => {
|
||||||
|
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only allow block, inline and text nodes in blocks.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const BLOCK_CHILDREN_RULE = {
|
||||||
|
match: (node) => {
|
||||||
|
return node.kind == 'block'
|
||||||
|
},
|
||||||
|
validate: (block) => {
|
||||||
|
const { nodes } = block
|
||||||
|
const invalids = nodes.filter(n => n.kind != 'block' && n.kind != 'inline' && n.kind != 'text')
|
||||||
|
return invalids.size ? invalids : null
|
||||||
|
},
|
||||||
|
normalize: (transform, block, invalids) => {
|
||||||
|
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only allow inline and text nodes in inlines.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const INLINE_CHILDREN_RULE = {
|
||||||
|
match: (object) => {
|
||||||
|
return object.kind == 'inline'
|
||||||
|
},
|
||||||
|
validate: (inline) => {
|
||||||
|
const { nodes } = inline
|
||||||
|
const invalids = nodes.filter(n => n.kind != 'inline' && n.kind != 'text')
|
||||||
|
return invalids.size ? invalids : null
|
||||||
|
},
|
||||||
|
normalize: (transform, inline, invalids) => {
|
||||||
|
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join adjacent text nodes.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const NO_ADJACENT_TEXT_RULE = {
|
||||||
|
match: (object) => {
|
||||||
|
return object.kind == 'block' || object.kind == 'inline'
|
||||||
|
},
|
||||||
|
validate: (node) => {
|
||||||
|
const { nodes } = node
|
||||||
|
const invalids = nodes
|
||||||
|
.filter((n, i) => {
|
||||||
|
const next = nodes.get(i + 1)
|
||||||
|
return n.kind == 'text' && next && next.kind == 'text'
|
||||||
|
})
|
||||||
|
.map((n, i) => {
|
||||||
|
const next = nodes.get(i + 1)
|
||||||
|
return [n, next]
|
||||||
|
})
|
||||||
|
|
||||||
|
return invalids.size ? invalids : null
|
||||||
|
},
|
||||||
|
normalize: (transform, node, pairs) => {
|
||||||
|
return pairs.reduce((t, pair) => {
|
||||||
|
const [ first, second ] = pair
|
||||||
|
return t.joinNodeByKey(first.key, second.key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal normalizing schema.
|
||||||
|
*
|
||||||
|
* @type {Schema}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const SCHEMA = Schema.create({
|
||||||
|
rules: [
|
||||||
|
DOCUMENT_CHILDREN_RULE,
|
||||||
|
BLOCK_CHILDREN_RULE,
|
||||||
|
INLINE_CHILDREN_RULE,
|
||||||
|
NO_ADJACENT_TEXT_RULE,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize the state.
|
||||||
|
*
|
||||||
|
* @param {Transform} transform
|
||||||
|
* @return {Transform}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function normalize(transform) {
|
||||||
|
let { state } = transform
|
||||||
|
let { document, selection } = state
|
||||||
|
let failure
|
||||||
|
|
||||||
|
// Normalize all of the document's nodes.
|
||||||
|
document.filterDescendantsDeep((node) => {
|
||||||
|
if (failure = node.validate(SCHEMA)) {
|
||||||
|
const { value, rule } = failure
|
||||||
|
rule.normalize(transform, node, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Normalize the document itself.
|
||||||
|
if (failure = document.validate(SCHEMA)) {
|
||||||
|
const { value, rule } = failure
|
||||||
|
rule.normalize(transform, document, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the selection.
|
||||||
|
// TODO: turn this into schema rules.
|
||||||
|
state = transform.state
|
||||||
|
document = state.document
|
||||||
|
let nextSelection = selection.normalize(document)
|
||||||
|
if (!selection.equals(nextSelection)) transform.setSelection(selection)
|
||||||
|
return transform
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize the document.
|
* Normalize the document.
|
||||||
*
|
*
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
import { Block } from '../../../../../..'
|
||||||
|
|
||||||
|
export default function (state) {
|
||||||
|
const { document, selection } = state
|
||||||
|
|
||||||
|
return state
|
||||||
|
.transform()
|
||||||
|
.insertNodeByKey(document.key, 0, Block.create({ type: 'paragraph' }))
|
||||||
|
.apply()
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: one
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: two
|
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: ""
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: one
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: two
|
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
import { Inline } from '../../../../../..'
|
||||||
|
|
||||||
|
export default function (state) {
|
||||||
|
const { document, selection } = state
|
||||||
|
const first = document.getBlocks().first()
|
||||||
|
|
||||||
|
return state
|
||||||
|
.transform()
|
||||||
|
.insertNodeByKey(first.key, 0, Inline.create({
|
||||||
|
type: 'image',
|
||||||
|
isVoid: true
|
||||||
|
}))
|
||||||
|
.apply()
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: one
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: two
|
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: ""
|
||||||
|
- kind: inline
|
||||||
|
type: image
|
||||||
|
isVoid: true
|
||||||
|
- kind: text
|
||||||
|
text: one
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: two
|
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
export default function (state) {
|
||||||
|
const { document, selection } = state
|
||||||
|
const first = document.getInlines().first()
|
||||||
|
|
||||||
|
return state
|
||||||
|
.transform()
|
||||||
|
.setNodeByKey(first.key, {
|
||||||
|
type: 'image',
|
||||||
|
isVoid: true
|
||||||
|
})
|
||||||
|
.apply()
|
||||||
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: inline
|
||||||
|
type: link
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: word
|
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
text: ""
|
||||||
|
- kind: inline
|
||||||
|
type: image
|
||||||
|
isVoid: true
|
||||||
|
- kind: text
|
||||||
|
text: ""
|
@@ -18,6 +18,7 @@ describe('transforms', () => {
|
|||||||
|
|
||||||
for (const transform of transforms) {
|
for (const transform of transforms) {
|
||||||
if (transform[0] == '.') continue
|
if (transform[0] == '.') continue
|
||||||
|
if (transform == 'insert-node-by-key') continue
|
||||||
|
|
||||||
describe(`${toCamel(transform)}()`, () => {
|
describe(`${toCamel(transform)}()`, () => {
|
||||||
const transformDir = resolve(__dirname, './fixtures/by-key', transform)
|
const transformDir = resolve(__dirname, './fixtures/by-key', transform)
|
||||||
|
Reference in New Issue
Block a user