slate-edit-code
Version:
A Slate plugin to handle code blocks editing.
287 lines (234 loc) • 8.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _slate = require('slate');
var _immutable = require('immutable');
var _utils = require('../utils');
/**
* Returns a validateNode function, handling validation specific to code blocks that
* cannot be expressed using the schema.
*/
// Old format for Slate rules
function validateNode(opts) {
var rules = [noOrphanLine(opts), onlyLine(opts), onlyText(opts), noMarks(opts)];
var validators = rules.map(toValidateNode);
return function validateTableNode(node) {
var changer = void 0;
validators.find(function (validator) {
changer = validator(node);
return Boolean(changer);
});
return changer;
};
}
// Convert an old rule definition to an individual plugin with on "validateNode"
function toValidateNode(rule) {
return function validateRule(node) {
if (!rule.match(node)) {
return undefined;
}
var validationResult = rule.validate(node);
if (validationResult == null) {
return undefined;
}
return function (change) {
return rule.normalize(change, node, validationResult);
};
};
}
/**
* A rule that ensure code lines are always children
* of a code block.
*/
function noOrphanLine(opts) {
return {
// Match all blocks that are not code blocks
match: function match(node) {
return (node.object === 'block' || node.object === 'document') && node.type !== opts.containerType;
},
validate: function validate(node) {
var codeLines = node.nodes.filter(function (n) {
return n.type === opts.lineType;
});
if (codeLines.isEmpty()) {
// All good
return null;
}
// Wrap the orphan lines
return {
toWrap: codeLines
};
},
/**
* Wrap the given blocks in code containers
* @param {List<Nodes>} value.toWrap
*/
normalize: function normalize(change, node, value) {
return value.toWrap.reduce(function (c, n) {
return c.wrapBlockByKey(n.key, opts.containerType);
}, change);
}
};
}
/**
* A rule that ensure code blocks only contain lines of code, and no marks
*/
function onlyLine(opts) {
return {
match: function match(node) {
return node.type === opts.containerType;
},
validate: function validate(node) {
var nodes = node.nodes;
var toAdd = (0, _immutable.List)();
var toRemove = (0, _immutable.List)();
nodes.forEach(function (child) {
if (child.object === 'text') {
var lines = (0, _utils.deserializeCode)(opts, child.text).nodes;
toAdd = toAdd.concat(lines);
toRemove.push(child);
} else if (child.type !== opts.lineType) {
toRemove.push(child);
}
});
if (toAdd.size || toRemove.size) {
return { toAdd: toAdd, toRemove: toRemove };
}
return null;
},
normalize: function normalize(change, node, _ref) {
var toAdd = _ref.toAdd,
toRemove = _ref.toRemove;
toAdd.forEach(function (child, index) {
change.insertNodeByKey(node.key, index, child);
});
toRemove.forEach(function (child) {
change.removeNodeByKey(child.key);
});
// Also remove marks here (since the no mark rule for
// lines will not be applied afterward).
return applyRule(noMarks(opts), change, node.key);
}
};
}
/**
* A rule that ensure code lines only contain one text
* node.
*/
function onlyText(opts) {
return {
match: function match(node) {
return node.type === opts.lineType;
},
validate: function validate(node) {
var nodes = node.nodes;
var toRemove = nodes.filterNot(function (n) {
return n.object === 'text';
});
if (!toRemove.isEmpty()) {
// Remove them, and the rest
// will be done in the next validation call.
return { toRemove: toRemove };
} else if (nodes.size > 1) {
// Else, there are only text nodes
return { toJoin: nodes };
} else if (nodes.size === 0) {
return { toAdd: [_slate.Text.create()] };
}
// There is a single text node -> valid
return null;
},
/**
* Clean up the child nodes.
*/
normalize: function normalize(change, node, _ref2) {
var _ref2$toRemove = _ref2.toRemove,
toRemove = _ref2$toRemove === undefined ? (0, _immutable.List)() : _ref2$toRemove,
_ref2$toAdd = _ref2.toAdd,
toAdd = _ref2$toAdd === undefined ? (0, _immutable.List)() : _ref2$toAdd,
_ref2$toJoin = _ref2.toJoin,
toJoin = _ref2$toJoin === undefined ? (0, _immutable.List)() : _ref2$toJoin;
// Remove invalids
toRemove.reduce(function (c, child) {
return c.removeNodeByKey(child.key, { normalize: false });
}, change);
// Join nodes.
var pairs = toJoin.butLast().map(function (child, index) {
return [child.key, toJoin.get(index + 1).key];
});
// Join every node onto the previous one.
pairs.reverse().reduce(function (c, _ref3) {
var _ref4 = _slicedToArray(_ref3, 2),
childKey = _ref4[0],
nextChildKey = _ref4[1];
return c.joinNodeByKey(nextChildKey, childKey, {
normalize: false
});
}, change);
// Add missing nodes
toAdd.reduce(function (c, child) {
return c.insertNodeByKey(node.key, 0, child);
}, change);
return change;
}
};
}
/**
* A rule that ensure code blocks contains no marks
*/
function noMarks(opts) {
return {
// Match at the line level, to optimize memoization
match: function match(node) {
return node.type === opts.lineType;
},
validate: function validate(node) {
if (opts.allowMarks) return null;
var marks = getMarks(node);
if (marks.isEmpty()) {
return null;
}
return {
removeMarks: marks
};
},
/**
* Removes the given marks
* @param {Set<Marks>} value.removeMarks
*/
normalize: function normalize(change, node, _ref5) {
var removeMarks = _ref5.removeMarks;
var selection = change.value.selection;
var range = selection.moveToRangeOf(node);
return removeMarks.reduce(function (c, mark) {
return c.removeMarkAtRange(range, mark);
}, change);
}
};
}
/**
* All the marks in the node
*/
function getMarks(node) {
var texts = node.getTexts();
var marks = texts.reduce(function (all, text) {
return text.characters.reduce(function (accu, chars) {
return accu.union(chars.marks);
}, all);
}, new _immutable.Set());
return marks;
}
/**
* Apply a normalization rule to a node
*/
function applyRule(rule, change, key) {
var node = change.value.document.getDescendant(key);
var notValid = rule.validate(node);
if (notValid) {
rule.normalize(change, node, notValid);
}
return change;
}
exports.default = validateNode;