slate-edit-list
Version:
A Slate plugin to handle keyboard events in lists.
162 lines (136 loc) • 4.7 kB
JavaScript
;
var Immutable = require('immutable');
var Set = Immutable.Set;
/**
* Create a schema for lists
* @param {String} opts.typeUL The type of unordered lists
* @param {String} opts.typeOL The type of ordered lists
* @param {String} opts.typeItem The type of list items
* @param {String} opts.typeDefault The type of the default block in list items
* @return {Object} A schema definition with rules to normalize lists
*/
function makeSchema(opts) {
return {
rules: [listsContainOnlyItems(opts), itemsDescendList(opts),
// Must be after itemsDescendList
itemsContainBlocks(opts)]
};
}
/**
* @param {String} opts.typeUL The type of unordered lists
* @param {String} opts.typeOL The type of ordered lists
* @param {String} opts.typeItem The type of list items
* @return {Object} A rule that ensure lists only contain list
* items, and at least one.
*/
function listsContainOnlyItems(opts) {
var isList = matchTypes([opts.typeUL, opts.typeOL]);
return {
match: isList,
validate: function validate(list) {
var notItems = list.nodes.filter(function (n) {
return n.type !== opts.typeItem;
});
if (notItems.isEmpty()) {
// Only valid list items
return null;
} else {
// All the non items
return {
toWrap: notItems
};
}
},
/**
* @param {List<Nodes>} value.toWrap Children to wrap in list
*/
normalize: function normalize(transform, node, value) {
return value.toWrap.reduce(function (tr, _ref) {
var key = _ref.key;
return tr.wrapBlockByKey(key, opts.typeItem);
}, transform);
}
};
}
/**
* @param {String} opts.typeUL The type of unordered lists
* @param {String} opts.typeOL The type of ordered lists
* @param {String} opts.typeItem The type of list items
* @return {Object} A rule that ensure list items are always children
* of a list block.
*/
function itemsDescendList(opts) {
var isList = matchTypes([opts.typeUL, opts.typeOL]);
return {
match: function match(node) {
return (node.kind === 'block' || node.kind === 'document') && !isList(node);
},
validate: function validate(block) {
var listItems = block.nodes.filter(function (n) {
return n.type === opts.typeItem;
});
if (listItems.isEmpty()) {
// No orphan list items. All good.
return null;
} else {
// Unwrap the orphan list items
return {
toUnwrap: listItems
};
}
},
/**
* Unwrap the given blocks
* @param {List<Nodes>} value.toUnwrap
*/
normalize: function normalize(transform, node, value) {
return value.toUnwrap.reduce(function (tr, n) {
return tr.unwrapBlockByKey(n.key);
}, transform);
}
};
}
/**
* @param {String} opts.typeItem The type of list items
* @param {String} opts.typeDefault The type of the default block in list items
* @return {Object} A rule that ensure list items always contain
* blocks.
*/
function itemsContainBlocks(opts) {
var isItem = matchTypes([opts.typeItem]);
return {
match: isItem,
validate: function validate(item) {
var shouldWrap = item.nodes.some(function (node) {
return node.kind !== 'block';
});
return shouldWrap || null;
},
/**
* Wraps the children nodes in the default block
*/
normalize: function normalize(transform, node, _) {
var noNorm = { normalize: false };
transform = transform.wrapBlockByKey(node.nodes.first().key, opts.typeDefault, noNorm);
var wrapper = transform.state.document.getDescendant(node.key).nodes.first();
// Add the remaining items
return node.nodes.rest().reduce(function (tr, n, index) {
return tr.moveNodeByKey(n.key, wrapper.key, index + 1, noNorm);
}, transform);
}
};
}
/**
* @param {Array<String>} types
* @return {Function} A function that returns true for nodes that
* match one of the given types.
*/
function matchTypes(types) {
types = new Set(types);
return function (node) {
return types.some(function (type) {
return type === node.type;
});
};
}
module.exports = makeSchema;