UNPKG

slate-edit-list

Version:

A Slate plugin to handle keyboard events in lists.

162 lines (136 loc) 4.7 kB
'use strict'; 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;