UNPKG

7.85 kBJavaScriptView Raw
1'use strict';
2
3var _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"); } }; }();
4
5var _require = require('immutable'),
6 Set = _require.Set,
7 List = _require.List;
8
9var Slate = require('slate');
10
11/**
12 * Create a schema for code blocks.
13 * @param {Options} opts
14 * @return {Object} A schema definition with normalization rules
15 */
16function makeSchema(opts) {
17 return {
18 rules: [noOrphanLine(opts), onlyLine(opts), onlyText(opts), noMarks(opts)]
19 };
20}
21
22/**
23 * @return {Object} A rule that ensure code lines are always children
24 * of a code block.
25 */
26function noOrphanLine(opts) {
27
28 return {
29 // Match all blocks that are not code blocks
30 match: function match(node) {
31 return (node.kind === 'block' || node.kind === 'document') && node.type !== opts.containerType;
32 },
33 validate: function validate(node) {
34 var codeLines = node.nodes.filter(function (n) {
35 return n.type === opts.lineType;
36 });
37
38 if (codeLines.isEmpty()) {
39 // All good
40 return null;
41 } else {
42 // Wrap the orphan lines
43 return {
44 toWrap: codeLines
45 };
46 }
47 },
48
49
50 /**
51 * Wrap the given blocks in code containers
52 * @param {List<Nodes>} value.toWrap
53 */
54 normalize: function normalize(change, node, value) {
55 return value.toWrap.reduce(function (c, n) {
56 return c.wrapBlockByKey(n.key, opts.containerType);
57 }, change);
58 }
59 };
60}
61
62/**
63 * @return {Object} A rule that ensure code blocks only contain lines of code, and no marks
64 */
65function onlyLine(opts) {
66 return {
67 match: function match(node) {
68 return node.type === opts.containerType;
69 },
70
71 validate: function validate(node) {
72 var nodes = node.nodes;
73
74
75 var toWrap = [];
76 var toRemove = [];
77
78 nodes.forEach(function (child) {
79 if (child.kind === 'text') toWrap.push(child);else if (child.type !== opts.lineType) toRemove.push(child);
80 });
81
82 if (toWrap.length || toRemove.length) {
83 return { toWrap: toWrap, toRemove: toRemove };
84 } else {
85 return null;
86 }
87 },
88 normalize: function normalize(change, node, _ref) {
89 var toWrap = _ref.toWrap,
90 toRemove = _ref.toRemove;
91
92 toRemove.forEach(function (child) {
93 change.removeNodeByKey(child.key);
94 });
95
96 toWrap.forEach(function (child) {
97 change.wrapBlockByKey(child.key, opts.lineType);
98 });
99
100 // Also remove marks here (since the no mark rule for
101 // lines will not be applied afterward).
102 return applyRule(noMarks(opts), change, node.key);
103 }
104 };
105}
106
107/**
108 * @return {Object} A rule that ensure code lines only contain one text
109 * node.
110 */
111function onlyText(opts) {
112 return {
113 match: function match(node) {
114 return node.type === opts.lineType;
115 },
116
117 validate: function validate(node) {
118 var nodes = node.nodes;
119
120
121 var toRemove = nodes.filterNot(function (n) {
122 return n.kind === 'text';
123 });
124 if (!toRemove.isEmpty()) {
125 // Remove them, and the rest
126 // will be done in the next validation call.
127 return { toRemove: toRemove };
128 }
129 // Else, there are only text nodes
130
131 else if (nodes.size > 1) {
132 return { toJoin: nodes };
133 } else if (nodes.size === 0) {
134 return { toAdd: [Slate.Text.create()] };
135 } else {
136 // There is a single text node -> valid
137 return null;
138 }
139 },
140
141
142 /**
143 * Clean up the child nodes.
144 */
145 normalize: function normalize(change, node, _ref2) {
146 var _ref2$toRemove = _ref2.toRemove,
147 toRemove = _ref2$toRemove === undefined ? List() : _ref2$toRemove,
148 _ref2$toAdd = _ref2.toAdd,
149 toAdd = _ref2$toAdd === undefined ? List() : _ref2$toAdd,
150 _ref2$toJoin = _ref2.toJoin,
151 toJoin = _ref2$toJoin === undefined ? List() : _ref2$toJoin;
152
153 // Remove invalids
154 toRemove.reduce(function (c, child) {
155 return c.removeNodeByKey(child.key, { normalize: false });
156 }, change);
157
158 // Join nodes.
159 var pairs = toJoin.butLast().map(function (child, index) {
160 return [child.key, toJoin.get(index + 1).key];
161 });
162
163 // Join every node onto the previous one.
164 pairs.reverse().reduce(function (c, _ref3) {
165 var _ref4 = _slicedToArray(_ref3, 2),
166 childKey = _ref4[0],
167 nextChildKey = _ref4[1];
168
169 return c.joinNodeByKey(nextChildKey, childKey, { normalize: false });
170 }, change);
171
172 // Add missing nodes
173 toAdd.reduce(function (c, child) {
174 return c.insertNodeByKey(node.key, 0, child);
175 }, change);
176
177 return change;
178 }
179 };
180}
181
182/**
183 * @return {Object} A rule that ensure code blocks contains no marks
184 */
185function noMarks(opts) {
186 return {
187 // Match at the line level, to optimize memoization
188 match: function match(node) {
189 return node.type === opts.lineType;
190 },
191
192 validate: function validate(node) {
193 if (opts.allowMarks) return null;
194
195 var marks = getMarks(node);
196
197 if (marks.isEmpty()) {
198 return null;
199 } else {
200 return {
201 removeMarks: marks
202 };
203 }
204 },
205
206
207 /**
208 * Removes the given marks
209 * @param {Set<Marks>} value.removeMarks
210 */
211 normalize: function normalize(change, node, _ref5) {
212 var removeMarks = _ref5.removeMarks;
213
214 var selection = change.state.selection;
215 var range = selection.moveToRangeOf(node);
216
217 return removeMarks.reduce(function (c, mark) {
218 return c.removeMarkAtRange(range, mark);
219 }, change);
220 }
221 };
222}
223
224/**
225 * @param {Node} node
226 * @return {Set<Marks>} All the marks in the node
227 */
228function getMarks(node) {
229 var texts = node.getTexts();
230
231 var marks = texts.reduce(function (all, text) {
232 return text.characters.reduce(function (accu, chars) {
233 return accu.union(chars.marks);
234 }, all);
235 }, new Set());
236
237 return marks;
238}
239
240/**
241 * Apply a normalization rule to a node
242 * @param {Rule} rule
243 * @param {Change} change
244 * @param {String} key
245 * @return {Change}
246 */
247function applyRule(rule, change, key) {
248 var node = change.state.document.getDescendant(key);
249 var notValid = rule.validate(node);
250 if (notValid) {
251 rule.normalize(change, node, notValid);
252 }
253
254 return change;
255}
256
257module.exports = makeSchema;
\No newline at end of file