@tiptap/extension-table 2.0.0-beta.203 → 2.0.0-beta.205
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tiptap-extension-table.cjs +4405 -23
- package/dist/tiptap-extension-table.cjs.map +1 -1
- package/dist/tiptap-extension-table.esm.js +4681 -0
- package/dist/tiptap-extension-table.esm.js.map +1 -0
- package/dist/tiptap-extension-table.umd.js +4407 -26
- package/dist/tiptap-extension-table.umd.js.map +1 -1
- package/package.json +9 -5
- package/dist/tiptap-extension-table.mjs +0 -299
- package/dist/tiptap-extension-table.mjs.map +0 -1
|
@@ -3,8 +3,4390 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var core = require('@tiptap/core');
|
|
6
|
-
var prosemirrorTables = require('@tiptap/prosemirror-tables');
|
|
7
6
|
var prosemirrorState = require('prosemirror-state');
|
|
7
|
+
var prosemirrorModel = require('prosemirror-model');
|
|
8
|
+
var prosemirrorView = require('prosemirror-view');
|
|
9
|
+
|
|
10
|
+
var base = {
|
|
11
|
+
8: "Backspace",
|
|
12
|
+
9: "Tab",
|
|
13
|
+
10: "Enter",
|
|
14
|
+
12: "NumLock",
|
|
15
|
+
13: "Enter",
|
|
16
|
+
16: "Shift",
|
|
17
|
+
17: "Control",
|
|
18
|
+
18: "Alt",
|
|
19
|
+
20: "CapsLock",
|
|
20
|
+
27: "Escape",
|
|
21
|
+
32: " ",
|
|
22
|
+
33: "PageUp",
|
|
23
|
+
34: "PageDown",
|
|
24
|
+
35: "End",
|
|
25
|
+
36: "Home",
|
|
26
|
+
37: "ArrowLeft",
|
|
27
|
+
38: "ArrowUp",
|
|
28
|
+
39: "ArrowRight",
|
|
29
|
+
40: "ArrowDown",
|
|
30
|
+
44: "PrintScreen",
|
|
31
|
+
45: "Insert",
|
|
32
|
+
46: "Delete",
|
|
33
|
+
59: ";",
|
|
34
|
+
61: "=",
|
|
35
|
+
91: "Meta",
|
|
36
|
+
92: "Meta",
|
|
37
|
+
106: "*",
|
|
38
|
+
107: "+",
|
|
39
|
+
108: ",",
|
|
40
|
+
109: "-",
|
|
41
|
+
110: ".",
|
|
42
|
+
111: "/",
|
|
43
|
+
144: "NumLock",
|
|
44
|
+
145: "ScrollLock",
|
|
45
|
+
160: "Shift",
|
|
46
|
+
161: "Shift",
|
|
47
|
+
162: "Control",
|
|
48
|
+
163: "Control",
|
|
49
|
+
164: "Alt",
|
|
50
|
+
165: "Alt",
|
|
51
|
+
173: "-",
|
|
52
|
+
186: ";",
|
|
53
|
+
187: "=",
|
|
54
|
+
188: ",",
|
|
55
|
+
189: "-",
|
|
56
|
+
190: ".",
|
|
57
|
+
191: "/",
|
|
58
|
+
192: "`",
|
|
59
|
+
219: "[",
|
|
60
|
+
220: "\\",
|
|
61
|
+
221: "]",
|
|
62
|
+
222: "'",
|
|
63
|
+
229: "q"
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
var shift = {
|
|
67
|
+
48: ")",
|
|
68
|
+
49: "!",
|
|
69
|
+
50: "@",
|
|
70
|
+
51: "#",
|
|
71
|
+
52: "$",
|
|
72
|
+
53: "%",
|
|
73
|
+
54: "^",
|
|
74
|
+
55: "&",
|
|
75
|
+
56: "*",
|
|
76
|
+
57: "(",
|
|
77
|
+
59: ":",
|
|
78
|
+
61: "+",
|
|
79
|
+
173: "_",
|
|
80
|
+
186: ":",
|
|
81
|
+
187: "+",
|
|
82
|
+
188: "<",
|
|
83
|
+
189: "_",
|
|
84
|
+
190: ">",
|
|
85
|
+
191: "?",
|
|
86
|
+
192: "~",
|
|
87
|
+
219: "{",
|
|
88
|
+
220: "|",
|
|
89
|
+
221: "}",
|
|
90
|
+
222: "\"",
|
|
91
|
+
229: "Q"
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
var chrome = typeof navigator != "undefined" && /Chrome\/(\d+)/.exec(navigator.userAgent);
|
|
95
|
+
var safari = typeof navigator != "undefined" && /Apple Computer/.test(navigator.vendor);
|
|
96
|
+
var gecko = typeof navigator != "undefined" && /Gecko\/\d+/.test(navigator.userAgent);
|
|
97
|
+
var mac$1 = typeof navigator != "undefined" && /Mac/.test(navigator.platform);
|
|
98
|
+
var ie = typeof navigator != "undefined" && /MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
|
|
99
|
+
var brokenModifierNames = chrome && (mac$1 || +chrome[1] < 57) || gecko && mac$1;
|
|
100
|
+
|
|
101
|
+
// Fill in the digit keys
|
|
102
|
+
for (var i = 0; i < 10; i++) base[48 + i] = base[96 + i] = String(i);
|
|
103
|
+
|
|
104
|
+
// The function keys
|
|
105
|
+
for (var i = 1; i <= 24; i++) base[i + 111] = "F" + i;
|
|
106
|
+
|
|
107
|
+
// And the alphabetic keys
|
|
108
|
+
for (var i = 65; i <= 90; i++) {
|
|
109
|
+
base[i] = String.fromCharCode(i + 32);
|
|
110
|
+
shift[i] = String.fromCharCode(i);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// For each code that doesn't have a shift-equivalent, copy the base name
|
|
114
|
+
for (var code in base) if (!shift.hasOwnProperty(code)) shift[code] = base[code];
|
|
115
|
+
|
|
116
|
+
function keyName(event) {
|
|
117
|
+
// Don't trust event.key in Chrome when there are modifiers until
|
|
118
|
+
// they fix https://bugs.chromium.org/p/chromium/issues/detail?id=633838
|
|
119
|
+
var ignoreKey = brokenModifierNames && (event.ctrlKey || event.altKey || event.metaKey) ||
|
|
120
|
+
(safari || ie) && event.shiftKey && event.key && event.key.length == 1;
|
|
121
|
+
var name = (!ignoreKey && event.key) ||
|
|
122
|
+
(event.shiftKey ? shift : base)[event.keyCode] ||
|
|
123
|
+
event.key || "Unidentified";
|
|
124
|
+
// Edge sometimes produces wrong names (Issue #3)
|
|
125
|
+
if (name == "Esc") name = "Escape";
|
|
126
|
+
if (name == "Del") name = "Delete";
|
|
127
|
+
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
|
|
128
|
+
if (name == "Left") name = "ArrowLeft";
|
|
129
|
+
if (name == "Up") name = "ArrowUp";
|
|
130
|
+
if (name == "Right") name = "ArrowRight";
|
|
131
|
+
if (name == "Down") name = "ArrowDown";
|
|
132
|
+
return name
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const mac = typeof navigator != "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform) : false;
|
|
136
|
+
function normalizeKeyName(name) {
|
|
137
|
+
let parts = name.split(/-(?!$)/), result = parts[parts.length - 1];
|
|
138
|
+
if (result == "Space")
|
|
139
|
+
result = " ";
|
|
140
|
+
let alt, ctrl, shift, meta;
|
|
141
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
142
|
+
let mod = parts[i];
|
|
143
|
+
if (/^(cmd|meta|m)$/i.test(mod))
|
|
144
|
+
meta = true;
|
|
145
|
+
else if (/^a(lt)?$/i.test(mod))
|
|
146
|
+
alt = true;
|
|
147
|
+
else if (/^(c|ctrl|control)$/i.test(mod))
|
|
148
|
+
ctrl = true;
|
|
149
|
+
else if (/^s(hift)?$/i.test(mod))
|
|
150
|
+
shift = true;
|
|
151
|
+
else if (/^mod$/i.test(mod)) {
|
|
152
|
+
if (mac)
|
|
153
|
+
meta = true;
|
|
154
|
+
else
|
|
155
|
+
ctrl = true;
|
|
156
|
+
}
|
|
157
|
+
else
|
|
158
|
+
throw new Error("Unrecognized modifier name: " + mod);
|
|
159
|
+
}
|
|
160
|
+
if (alt)
|
|
161
|
+
result = "Alt-" + result;
|
|
162
|
+
if (ctrl)
|
|
163
|
+
result = "Ctrl-" + result;
|
|
164
|
+
if (meta)
|
|
165
|
+
result = "Meta-" + result;
|
|
166
|
+
if (shift)
|
|
167
|
+
result = "Shift-" + result;
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
function normalize(map) {
|
|
171
|
+
let copy = Object.create(null);
|
|
172
|
+
for (let prop in map)
|
|
173
|
+
copy[normalizeKeyName(prop)] = map[prop];
|
|
174
|
+
return copy;
|
|
175
|
+
}
|
|
176
|
+
function modifiers(name, event, shift) {
|
|
177
|
+
if (event.altKey)
|
|
178
|
+
name = "Alt-" + name;
|
|
179
|
+
if (event.ctrlKey)
|
|
180
|
+
name = "Ctrl-" + name;
|
|
181
|
+
if (event.metaKey)
|
|
182
|
+
name = "Meta-" + name;
|
|
183
|
+
if (shift !== false && event.shiftKey)
|
|
184
|
+
name = "Shift-" + name;
|
|
185
|
+
return name;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
Given a set of bindings (using the same format as
|
|
189
|
+
[`keymap`](https://prosemirror.net/docs/ref/#keymap.keymap)), return a [keydown
|
|
190
|
+
handler](https://prosemirror.net/docs/ref/#view.EditorProps.handleKeyDown) that handles them.
|
|
191
|
+
*/
|
|
192
|
+
function keydownHandler(bindings) {
|
|
193
|
+
let map = normalize(bindings);
|
|
194
|
+
return function (view, event) {
|
|
195
|
+
let name = keyName(event), isChar = name.length == 1 && name != " ", baseName;
|
|
196
|
+
let direct = map[modifiers(name, event, !isChar)];
|
|
197
|
+
if (direct && direct(view.state, view.dispatch, view))
|
|
198
|
+
return true;
|
|
199
|
+
if (isChar && (event.shiftKey || event.altKey || event.metaKey || name.charCodeAt(0) > 127) &&
|
|
200
|
+
(baseName = base[event.keyCode]) && baseName != name) {
|
|
201
|
+
// Try falling back to the keyCode when there's a modifier
|
|
202
|
+
// active or the character produced isn't ASCII, and our table
|
|
203
|
+
// produces a different name from the the keyCode. See #668,
|
|
204
|
+
// #1060
|
|
205
|
+
let fromCode = map[modifiers(baseName, event, true)];
|
|
206
|
+
if (fromCode && fromCode(view.state, view.dispatch, view))
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
else if (isChar && event.shiftKey) {
|
|
210
|
+
// Otherwise, if shift is active, also try the binding with the
|
|
211
|
+
// Shift- prefix enabled. See #997
|
|
212
|
+
let withShift = map[modifiers(name, event, true)];
|
|
213
|
+
if (withShift && withShift(view.state, view.dispatch, view))
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Recovery values encode a range index and an offset. They are
|
|
221
|
+
// represented as numbers, because tons of them will be created when
|
|
222
|
+
// mapping, for example, a large number of decorations. The number's
|
|
223
|
+
// lower 16 bits provide the index, the remaining bits the offset.
|
|
224
|
+
//
|
|
225
|
+
// Note: We intentionally don't use bit shift operators to en- and
|
|
226
|
+
// decode these, since those clip to 32 bits, which we might in rare
|
|
227
|
+
// cases want to overflow. A 64-bit float can represent 48-bit
|
|
228
|
+
// integers precisely.
|
|
229
|
+
const lower16 = 0xffff;
|
|
230
|
+
const factor16 = Math.pow(2, 16);
|
|
231
|
+
function makeRecover(index, offset) { return index + offset * factor16; }
|
|
232
|
+
function recoverIndex(value) { return value & lower16; }
|
|
233
|
+
function recoverOffset(value) { return (value - (value & lower16)) / factor16; }
|
|
234
|
+
const DEL_BEFORE = 1, DEL_AFTER = 2, DEL_ACROSS = 4, DEL_SIDE = 8;
|
|
235
|
+
/**
|
|
236
|
+
An object representing a mapped position with extra
|
|
237
|
+
information.
|
|
238
|
+
*/
|
|
239
|
+
class MapResult {
|
|
240
|
+
/**
|
|
241
|
+
@internal
|
|
242
|
+
*/
|
|
243
|
+
constructor(
|
|
244
|
+
/**
|
|
245
|
+
The mapped version of the position.
|
|
246
|
+
*/
|
|
247
|
+
pos,
|
|
248
|
+
/**
|
|
249
|
+
@internal
|
|
250
|
+
*/
|
|
251
|
+
delInfo,
|
|
252
|
+
/**
|
|
253
|
+
@internal
|
|
254
|
+
*/
|
|
255
|
+
recover) {
|
|
256
|
+
this.pos = pos;
|
|
257
|
+
this.delInfo = delInfo;
|
|
258
|
+
this.recover = recover;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
Tells you whether the position was deleted, that is, whether the
|
|
262
|
+
step removed the token on the side queried (via the `assoc`)
|
|
263
|
+
argument from the document.
|
|
264
|
+
*/
|
|
265
|
+
get deleted() { return (this.delInfo & DEL_SIDE) > 0; }
|
|
266
|
+
/**
|
|
267
|
+
Tells you whether the token before the mapped position was deleted.
|
|
268
|
+
*/
|
|
269
|
+
get deletedBefore() { return (this.delInfo & (DEL_BEFORE | DEL_ACROSS)) > 0; }
|
|
270
|
+
/**
|
|
271
|
+
True when the token after the mapped position was deleted.
|
|
272
|
+
*/
|
|
273
|
+
get deletedAfter() { return (this.delInfo & (DEL_AFTER | DEL_ACROSS)) > 0; }
|
|
274
|
+
/**
|
|
275
|
+
Tells whether any of the steps mapped through deletes across the
|
|
276
|
+
position (including both the token before and after the
|
|
277
|
+
position).
|
|
278
|
+
*/
|
|
279
|
+
get deletedAcross() { return (this.delInfo & DEL_ACROSS) > 0; }
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
A map describing the deletions and insertions made by a step, which
|
|
283
|
+
can be used to find the correspondence between positions in the
|
|
284
|
+
pre-step version of a document and the same position in the
|
|
285
|
+
post-step version.
|
|
286
|
+
*/
|
|
287
|
+
class StepMap {
|
|
288
|
+
/**
|
|
289
|
+
Create a position map. The modifications to the document are
|
|
290
|
+
represented as an array of numbers, in which each group of three
|
|
291
|
+
represents a modified chunk as `[start, oldSize, newSize]`.
|
|
292
|
+
*/
|
|
293
|
+
constructor(
|
|
294
|
+
/**
|
|
295
|
+
@internal
|
|
296
|
+
*/
|
|
297
|
+
ranges,
|
|
298
|
+
/**
|
|
299
|
+
@internal
|
|
300
|
+
*/
|
|
301
|
+
inverted = false) {
|
|
302
|
+
this.ranges = ranges;
|
|
303
|
+
this.inverted = inverted;
|
|
304
|
+
if (!ranges.length && StepMap.empty)
|
|
305
|
+
return StepMap.empty;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
@internal
|
|
309
|
+
*/
|
|
310
|
+
recover(value) {
|
|
311
|
+
let diff = 0, index = recoverIndex(value);
|
|
312
|
+
if (!this.inverted)
|
|
313
|
+
for (let i = 0; i < index; i++)
|
|
314
|
+
diff += this.ranges[i * 3 + 2] - this.ranges[i * 3 + 1];
|
|
315
|
+
return this.ranges[index * 3] + diff + recoverOffset(value);
|
|
316
|
+
}
|
|
317
|
+
mapResult(pos, assoc = 1) { return this._map(pos, assoc, false); }
|
|
318
|
+
map(pos, assoc = 1) { return this._map(pos, assoc, true); }
|
|
319
|
+
/**
|
|
320
|
+
@internal
|
|
321
|
+
*/
|
|
322
|
+
_map(pos, assoc, simple) {
|
|
323
|
+
let diff = 0, oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2;
|
|
324
|
+
for (let i = 0; i < this.ranges.length; i += 3) {
|
|
325
|
+
let start = this.ranges[i] - (this.inverted ? diff : 0);
|
|
326
|
+
if (start > pos)
|
|
327
|
+
break;
|
|
328
|
+
let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex], end = start + oldSize;
|
|
329
|
+
if (pos <= end) {
|
|
330
|
+
let side = !oldSize ? assoc : pos == start ? -1 : pos == end ? 1 : assoc;
|
|
331
|
+
let result = start + diff + (side < 0 ? 0 : newSize);
|
|
332
|
+
if (simple)
|
|
333
|
+
return result;
|
|
334
|
+
let recover = pos == (assoc < 0 ? start : end) ? null : makeRecover(i / 3, pos - start);
|
|
335
|
+
let del = pos == start ? DEL_AFTER : pos == end ? DEL_BEFORE : DEL_ACROSS;
|
|
336
|
+
if (assoc < 0 ? pos != start : pos != end)
|
|
337
|
+
del |= DEL_SIDE;
|
|
338
|
+
return new MapResult(result, del, recover);
|
|
339
|
+
}
|
|
340
|
+
diff += newSize - oldSize;
|
|
341
|
+
}
|
|
342
|
+
return simple ? pos + diff : new MapResult(pos + diff, 0, null);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
@internal
|
|
346
|
+
*/
|
|
347
|
+
touches(pos, recover) {
|
|
348
|
+
let diff = 0, index = recoverIndex(recover);
|
|
349
|
+
let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2;
|
|
350
|
+
for (let i = 0; i < this.ranges.length; i += 3) {
|
|
351
|
+
let start = this.ranges[i] - (this.inverted ? diff : 0);
|
|
352
|
+
if (start > pos)
|
|
353
|
+
break;
|
|
354
|
+
let oldSize = this.ranges[i + oldIndex], end = start + oldSize;
|
|
355
|
+
if (pos <= end && i == index * 3)
|
|
356
|
+
return true;
|
|
357
|
+
diff += this.ranges[i + newIndex] - oldSize;
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
Calls the given function on each of the changed ranges included in
|
|
363
|
+
this map.
|
|
364
|
+
*/
|
|
365
|
+
forEach(f) {
|
|
366
|
+
let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2;
|
|
367
|
+
for (let i = 0, diff = 0; i < this.ranges.length; i += 3) {
|
|
368
|
+
let start = this.ranges[i], oldStart = start - (this.inverted ? diff : 0), newStart = start + (this.inverted ? 0 : diff);
|
|
369
|
+
let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex];
|
|
370
|
+
f(oldStart, oldStart + oldSize, newStart, newStart + newSize);
|
|
371
|
+
diff += newSize - oldSize;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
Create an inverted version of this map. The result can be used to
|
|
376
|
+
map positions in the post-step document to the pre-step document.
|
|
377
|
+
*/
|
|
378
|
+
invert() {
|
|
379
|
+
return new StepMap(this.ranges, !this.inverted);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
@internal
|
|
383
|
+
*/
|
|
384
|
+
toString() {
|
|
385
|
+
return (this.inverted ? "-" : "") + JSON.stringify(this.ranges);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
Create a map that moves all positions by offset `n` (which may be
|
|
389
|
+
negative). This can be useful when applying steps meant for a
|
|
390
|
+
sub-document to a larger document, or vice-versa.
|
|
391
|
+
*/
|
|
392
|
+
static offset(n) {
|
|
393
|
+
return n == 0 ? StepMap.empty : new StepMap(n < 0 ? [0, -n, 0] : [0, 0, n]);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
A StepMap that contains no changed ranges.
|
|
398
|
+
*/
|
|
399
|
+
StepMap.empty = new StepMap([]);
|
|
400
|
+
/**
|
|
401
|
+
A mapping represents a pipeline of zero or more [step
|
|
402
|
+
maps](https://prosemirror.net/docs/ref/#transform.StepMap). It has special provisions for losslessly
|
|
403
|
+
handling mapping positions through a series of steps in which some
|
|
404
|
+
steps are inverted versions of earlier steps. (This comes up when
|
|
405
|
+
‘[rebasing](/docs/guide/#transform.rebasing)’ steps for
|
|
406
|
+
collaboration or history management.)
|
|
407
|
+
*/
|
|
408
|
+
class Mapping {
|
|
409
|
+
/**
|
|
410
|
+
Create a new mapping with the given position maps.
|
|
411
|
+
*/
|
|
412
|
+
constructor(
|
|
413
|
+
/**
|
|
414
|
+
The step maps in this mapping.
|
|
415
|
+
*/
|
|
416
|
+
maps = [],
|
|
417
|
+
/**
|
|
418
|
+
@internal
|
|
419
|
+
*/
|
|
420
|
+
mirror,
|
|
421
|
+
/**
|
|
422
|
+
The starting position in the `maps` array, used when `map` or
|
|
423
|
+
`mapResult` is called.
|
|
424
|
+
*/
|
|
425
|
+
from = 0,
|
|
426
|
+
/**
|
|
427
|
+
The end position in the `maps` array.
|
|
428
|
+
*/
|
|
429
|
+
to = maps.length) {
|
|
430
|
+
this.maps = maps;
|
|
431
|
+
this.mirror = mirror;
|
|
432
|
+
this.from = from;
|
|
433
|
+
this.to = to;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
Create a mapping that maps only through a part of this one.
|
|
437
|
+
*/
|
|
438
|
+
slice(from = 0, to = this.maps.length) {
|
|
439
|
+
return new Mapping(this.maps, this.mirror, from, to);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
@internal
|
|
443
|
+
*/
|
|
444
|
+
copy() {
|
|
445
|
+
return new Mapping(this.maps.slice(), this.mirror && this.mirror.slice(), this.from, this.to);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
Add a step map to the end of this mapping. If `mirrors` is
|
|
449
|
+
given, it should be the index of the step map that is the mirror
|
|
450
|
+
image of this one.
|
|
451
|
+
*/
|
|
452
|
+
appendMap(map, mirrors) {
|
|
453
|
+
this.to = this.maps.push(map);
|
|
454
|
+
if (mirrors != null)
|
|
455
|
+
this.setMirror(this.maps.length - 1, mirrors);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
Add all the step maps in a given mapping to this one (preserving
|
|
459
|
+
mirroring information).
|
|
460
|
+
*/
|
|
461
|
+
appendMapping(mapping) {
|
|
462
|
+
for (let i = 0, startSize = this.maps.length; i < mapping.maps.length; i++) {
|
|
463
|
+
let mirr = mapping.getMirror(i);
|
|
464
|
+
this.appendMap(mapping.maps[i], mirr != null && mirr < i ? startSize + mirr : undefined);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
Finds the offset of the step map that mirrors the map at the
|
|
469
|
+
given offset, in this mapping (as per the second argument to
|
|
470
|
+
`appendMap`).
|
|
471
|
+
*/
|
|
472
|
+
getMirror(n) {
|
|
473
|
+
if (this.mirror)
|
|
474
|
+
for (let i = 0; i < this.mirror.length; i++)
|
|
475
|
+
if (this.mirror[i] == n)
|
|
476
|
+
return this.mirror[i + (i % 2 ? -1 : 1)];
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
@internal
|
|
480
|
+
*/
|
|
481
|
+
setMirror(n, m) {
|
|
482
|
+
if (!this.mirror)
|
|
483
|
+
this.mirror = [];
|
|
484
|
+
this.mirror.push(n, m);
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
Append the inverse of the given mapping to this one.
|
|
488
|
+
*/
|
|
489
|
+
appendMappingInverted(mapping) {
|
|
490
|
+
for (let i = mapping.maps.length - 1, totalSize = this.maps.length + mapping.maps.length; i >= 0; i--) {
|
|
491
|
+
let mirr = mapping.getMirror(i);
|
|
492
|
+
this.appendMap(mapping.maps[i].invert(), mirr != null && mirr > i ? totalSize - mirr - 1 : undefined);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
Create an inverted version of this mapping.
|
|
497
|
+
*/
|
|
498
|
+
invert() {
|
|
499
|
+
let inverse = new Mapping;
|
|
500
|
+
inverse.appendMappingInverted(this);
|
|
501
|
+
return inverse;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
Map a position through this mapping.
|
|
505
|
+
*/
|
|
506
|
+
map(pos, assoc = 1) {
|
|
507
|
+
if (this.mirror)
|
|
508
|
+
return this._map(pos, assoc, true);
|
|
509
|
+
for (let i = this.from; i < this.to; i++)
|
|
510
|
+
pos = this.maps[i].map(pos, assoc);
|
|
511
|
+
return pos;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
Map a position through this mapping, returning a mapping
|
|
515
|
+
result.
|
|
516
|
+
*/
|
|
517
|
+
mapResult(pos, assoc = 1) { return this._map(pos, assoc, false); }
|
|
518
|
+
/**
|
|
519
|
+
@internal
|
|
520
|
+
*/
|
|
521
|
+
_map(pos, assoc, simple) {
|
|
522
|
+
let delInfo = 0;
|
|
523
|
+
for (let i = this.from; i < this.to; i++) {
|
|
524
|
+
let map = this.maps[i], result = map.mapResult(pos, assoc);
|
|
525
|
+
if (result.recover != null) {
|
|
526
|
+
let corr = this.getMirror(i);
|
|
527
|
+
if (corr != null && corr > i && corr < this.to) {
|
|
528
|
+
i = corr;
|
|
529
|
+
pos = this.maps[corr].recover(result.recover);
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
delInfo |= result.delInfo;
|
|
534
|
+
pos = result.pos;
|
|
535
|
+
}
|
|
536
|
+
return simple ? pos : new MapResult(pos, delInfo, null);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const stepsByID = Object.create(null);
|
|
541
|
+
/**
|
|
542
|
+
A step object represents an atomic change. It generally applies
|
|
543
|
+
only to the document it was created for, since the positions
|
|
544
|
+
stored in it will only make sense for that document.
|
|
545
|
+
|
|
546
|
+
New steps are defined by creating classes that extend `Step`,
|
|
547
|
+
overriding the `apply`, `invert`, `map`, `getMap` and `fromJSON`
|
|
548
|
+
methods, and registering your class with a unique
|
|
549
|
+
JSON-serialization identifier using
|
|
550
|
+
[`Step.jsonID`](https://prosemirror.net/docs/ref/#transform.Step^jsonID).
|
|
551
|
+
*/
|
|
552
|
+
class Step {
|
|
553
|
+
/**
|
|
554
|
+
Get the step map that represents the changes made by this step,
|
|
555
|
+
and which can be used to transform between positions in the old
|
|
556
|
+
and the new document.
|
|
557
|
+
*/
|
|
558
|
+
getMap() { return StepMap.empty; }
|
|
559
|
+
/**
|
|
560
|
+
Try to merge this step with another one, to be applied directly
|
|
561
|
+
after it. Returns the merged step when possible, null if the
|
|
562
|
+
steps can't be merged.
|
|
563
|
+
*/
|
|
564
|
+
merge(other) { return null; }
|
|
565
|
+
/**
|
|
566
|
+
Deserialize a step from its JSON representation. Will call
|
|
567
|
+
through to the step class' own implementation of this method.
|
|
568
|
+
*/
|
|
569
|
+
static fromJSON(schema, json) {
|
|
570
|
+
if (!json || !json.stepType)
|
|
571
|
+
throw new RangeError("Invalid input for Step.fromJSON");
|
|
572
|
+
let type = stepsByID[json.stepType];
|
|
573
|
+
if (!type)
|
|
574
|
+
throw new RangeError(`No step type ${json.stepType} defined`);
|
|
575
|
+
return type.fromJSON(schema, json);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
To be able to serialize steps to JSON, each step needs a string
|
|
579
|
+
ID to attach to its JSON representation. Use this method to
|
|
580
|
+
register an ID for your step classes. Try to pick something
|
|
581
|
+
that's unlikely to clash with steps from other modules.
|
|
582
|
+
*/
|
|
583
|
+
static jsonID(id, stepClass) {
|
|
584
|
+
if (id in stepsByID)
|
|
585
|
+
throw new RangeError("Duplicate use of step JSON ID " + id);
|
|
586
|
+
stepsByID[id] = stepClass;
|
|
587
|
+
stepClass.prototype.jsonID = id;
|
|
588
|
+
return stepClass;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
The result of [applying](https://prosemirror.net/docs/ref/#transform.Step.apply) a step. Contains either a
|
|
593
|
+
new document or a failure value.
|
|
594
|
+
*/
|
|
595
|
+
class StepResult {
|
|
596
|
+
/**
|
|
597
|
+
@internal
|
|
598
|
+
*/
|
|
599
|
+
constructor(
|
|
600
|
+
/**
|
|
601
|
+
The transformed document, if successful.
|
|
602
|
+
*/
|
|
603
|
+
doc,
|
|
604
|
+
/**
|
|
605
|
+
The failure message, if unsuccessful.
|
|
606
|
+
*/
|
|
607
|
+
failed) {
|
|
608
|
+
this.doc = doc;
|
|
609
|
+
this.failed = failed;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
Create a successful step result.
|
|
613
|
+
*/
|
|
614
|
+
static ok(doc) { return new StepResult(doc, null); }
|
|
615
|
+
/**
|
|
616
|
+
Create a failed step result.
|
|
617
|
+
*/
|
|
618
|
+
static fail(message) { return new StepResult(null, message); }
|
|
619
|
+
/**
|
|
620
|
+
Call [`Node.replace`](https://prosemirror.net/docs/ref/#model.Node.replace) with the given
|
|
621
|
+
arguments. Create a successful result if it succeeds, and a
|
|
622
|
+
failed one if it throws a `ReplaceError`.
|
|
623
|
+
*/
|
|
624
|
+
static fromReplace(doc, from, to, slice) {
|
|
625
|
+
try {
|
|
626
|
+
return StepResult.ok(doc.replace(from, to, slice));
|
|
627
|
+
}
|
|
628
|
+
catch (e) {
|
|
629
|
+
if (e instanceof prosemirrorModel.ReplaceError)
|
|
630
|
+
return StepResult.fail(e.message);
|
|
631
|
+
throw e;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function mapFragment(fragment, f, parent) {
|
|
637
|
+
let mapped = [];
|
|
638
|
+
for (let i = 0; i < fragment.childCount; i++) {
|
|
639
|
+
let child = fragment.child(i);
|
|
640
|
+
if (child.content.size)
|
|
641
|
+
child = child.copy(mapFragment(child.content, f, child));
|
|
642
|
+
if (child.isInline)
|
|
643
|
+
child = f(child, parent, i);
|
|
644
|
+
mapped.push(child);
|
|
645
|
+
}
|
|
646
|
+
return prosemirrorModel.Fragment.fromArray(mapped);
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
Add a mark to all inline content between two positions.
|
|
650
|
+
*/
|
|
651
|
+
class AddMarkStep extends Step {
|
|
652
|
+
/**
|
|
653
|
+
Create a mark step.
|
|
654
|
+
*/
|
|
655
|
+
constructor(
|
|
656
|
+
/**
|
|
657
|
+
The start of the marked range.
|
|
658
|
+
*/
|
|
659
|
+
from,
|
|
660
|
+
/**
|
|
661
|
+
The end of the marked range.
|
|
662
|
+
*/
|
|
663
|
+
to,
|
|
664
|
+
/**
|
|
665
|
+
The mark to add.
|
|
666
|
+
*/
|
|
667
|
+
mark) {
|
|
668
|
+
super();
|
|
669
|
+
this.from = from;
|
|
670
|
+
this.to = to;
|
|
671
|
+
this.mark = mark;
|
|
672
|
+
}
|
|
673
|
+
apply(doc) {
|
|
674
|
+
let oldSlice = doc.slice(this.from, this.to), $from = doc.resolve(this.from);
|
|
675
|
+
let parent = $from.node($from.sharedDepth(this.to));
|
|
676
|
+
let slice = new prosemirrorModel.Slice(mapFragment(oldSlice.content, (node, parent) => {
|
|
677
|
+
if (!node.isAtom || !parent.type.allowsMarkType(this.mark.type))
|
|
678
|
+
return node;
|
|
679
|
+
return node.mark(this.mark.addToSet(node.marks));
|
|
680
|
+
}, parent), oldSlice.openStart, oldSlice.openEnd);
|
|
681
|
+
return StepResult.fromReplace(doc, this.from, this.to, slice);
|
|
682
|
+
}
|
|
683
|
+
invert() {
|
|
684
|
+
return new RemoveMarkStep(this.from, this.to, this.mark);
|
|
685
|
+
}
|
|
686
|
+
map(mapping) {
|
|
687
|
+
let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
|
|
688
|
+
if (from.deleted && to.deleted || from.pos >= to.pos)
|
|
689
|
+
return null;
|
|
690
|
+
return new AddMarkStep(from.pos, to.pos, this.mark);
|
|
691
|
+
}
|
|
692
|
+
merge(other) {
|
|
693
|
+
if (other instanceof AddMarkStep &&
|
|
694
|
+
other.mark.eq(this.mark) &&
|
|
695
|
+
this.from <= other.to && this.to >= other.from)
|
|
696
|
+
return new AddMarkStep(Math.min(this.from, other.from), Math.max(this.to, other.to), this.mark);
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
toJSON() {
|
|
700
|
+
return { stepType: "addMark", mark: this.mark.toJSON(),
|
|
701
|
+
from: this.from, to: this.to };
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
@internal
|
|
705
|
+
*/
|
|
706
|
+
static fromJSON(schema, json) {
|
|
707
|
+
if (typeof json.from != "number" || typeof json.to != "number")
|
|
708
|
+
throw new RangeError("Invalid input for AddMarkStep.fromJSON");
|
|
709
|
+
return new AddMarkStep(json.from, json.to, schema.markFromJSON(json.mark));
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
Step.jsonID("addMark", AddMarkStep);
|
|
713
|
+
/**
|
|
714
|
+
Remove a mark from all inline content between two positions.
|
|
715
|
+
*/
|
|
716
|
+
class RemoveMarkStep extends Step {
|
|
717
|
+
/**
|
|
718
|
+
Create a mark-removing step.
|
|
719
|
+
*/
|
|
720
|
+
constructor(
|
|
721
|
+
/**
|
|
722
|
+
The start of the unmarked range.
|
|
723
|
+
*/
|
|
724
|
+
from,
|
|
725
|
+
/**
|
|
726
|
+
The end of the unmarked range.
|
|
727
|
+
*/
|
|
728
|
+
to,
|
|
729
|
+
/**
|
|
730
|
+
The mark to remove.
|
|
731
|
+
*/
|
|
732
|
+
mark) {
|
|
733
|
+
super();
|
|
734
|
+
this.from = from;
|
|
735
|
+
this.to = to;
|
|
736
|
+
this.mark = mark;
|
|
737
|
+
}
|
|
738
|
+
apply(doc) {
|
|
739
|
+
let oldSlice = doc.slice(this.from, this.to);
|
|
740
|
+
let slice = new prosemirrorModel.Slice(mapFragment(oldSlice.content, node => {
|
|
741
|
+
return node.mark(this.mark.removeFromSet(node.marks));
|
|
742
|
+
}, doc), oldSlice.openStart, oldSlice.openEnd);
|
|
743
|
+
return StepResult.fromReplace(doc, this.from, this.to, slice);
|
|
744
|
+
}
|
|
745
|
+
invert() {
|
|
746
|
+
return new AddMarkStep(this.from, this.to, this.mark);
|
|
747
|
+
}
|
|
748
|
+
map(mapping) {
|
|
749
|
+
let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
|
|
750
|
+
if (from.deleted && to.deleted || from.pos >= to.pos)
|
|
751
|
+
return null;
|
|
752
|
+
return new RemoveMarkStep(from.pos, to.pos, this.mark);
|
|
753
|
+
}
|
|
754
|
+
merge(other) {
|
|
755
|
+
if (other instanceof RemoveMarkStep &&
|
|
756
|
+
other.mark.eq(this.mark) &&
|
|
757
|
+
this.from <= other.to && this.to >= other.from)
|
|
758
|
+
return new RemoveMarkStep(Math.min(this.from, other.from), Math.max(this.to, other.to), this.mark);
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
toJSON() {
|
|
762
|
+
return { stepType: "removeMark", mark: this.mark.toJSON(),
|
|
763
|
+
from: this.from, to: this.to };
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
@internal
|
|
767
|
+
*/
|
|
768
|
+
static fromJSON(schema, json) {
|
|
769
|
+
if (typeof json.from != "number" || typeof json.to != "number")
|
|
770
|
+
throw new RangeError("Invalid input for RemoveMarkStep.fromJSON");
|
|
771
|
+
return new RemoveMarkStep(json.from, json.to, schema.markFromJSON(json.mark));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
Step.jsonID("removeMark", RemoveMarkStep);
|
|
775
|
+
/**
|
|
776
|
+
Add a mark to a specific node.
|
|
777
|
+
*/
|
|
778
|
+
class AddNodeMarkStep extends Step {
|
|
779
|
+
/**
|
|
780
|
+
Create a node mark step.
|
|
781
|
+
*/
|
|
782
|
+
constructor(
|
|
783
|
+
/**
|
|
784
|
+
The position of the target node.
|
|
785
|
+
*/
|
|
786
|
+
pos,
|
|
787
|
+
/**
|
|
788
|
+
The mark to add.
|
|
789
|
+
*/
|
|
790
|
+
mark) {
|
|
791
|
+
super();
|
|
792
|
+
this.pos = pos;
|
|
793
|
+
this.mark = mark;
|
|
794
|
+
}
|
|
795
|
+
apply(doc) {
|
|
796
|
+
let node = doc.nodeAt(this.pos);
|
|
797
|
+
if (!node)
|
|
798
|
+
return StepResult.fail("No node at mark step's position");
|
|
799
|
+
let updated = node.type.create(node.attrs, null, this.mark.addToSet(node.marks));
|
|
800
|
+
return StepResult.fromReplace(doc, this.pos, this.pos + 1, new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(updated), 0, node.isLeaf ? 0 : 1));
|
|
801
|
+
}
|
|
802
|
+
invert(doc) {
|
|
803
|
+
let node = doc.nodeAt(this.pos);
|
|
804
|
+
if (node) {
|
|
805
|
+
let newSet = this.mark.addToSet(node.marks);
|
|
806
|
+
if (newSet.length == node.marks.length) {
|
|
807
|
+
for (let i = 0; i < node.marks.length; i++)
|
|
808
|
+
if (!node.marks[i].isInSet(newSet))
|
|
809
|
+
return new AddNodeMarkStep(this.pos, node.marks[i]);
|
|
810
|
+
return new AddNodeMarkStep(this.pos, this.mark);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return new RemoveNodeMarkStep(this.pos, this.mark);
|
|
814
|
+
}
|
|
815
|
+
map(mapping) {
|
|
816
|
+
let pos = mapping.mapResult(this.pos, 1);
|
|
817
|
+
return pos.deletedAfter ? null : new AddNodeMarkStep(pos.pos, this.mark);
|
|
818
|
+
}
|
|
819
|
+
toJSON() {
|
|
820
|
+
return { stepType: "addNodeMark", pos: this.pos, mark: this.mark.toJSON() };
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
@internal
|
|
824
|
+
*/
|
|
825
|
+
static fromJSON(schema, json) {
|
|
826
|
+
if (typeof json.pos != "number")
|
|
827
|
+
throw new RangeError("Invalid input for AddNodeMarkStep.fromJSON");
|
|
828
|
+
return new AddNodeMarkStep(json.pos, schema.markFromJSON(json.mark));
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
Step.jsonID("addNodeMark", AddNodeMarkStep);
|
|
832
|
+
/**
|
|
833
|
+
Remove a mark from a specific node.
|
|
834
|
+
*/
|
|
835
|
+
class RemoveNodeMarkStep extends Step {
|
|
836
|
+
/**
|
|
837
|
+
Create a mark-removing step.
|
|
838
|
+
*/
|
|
839
|
+
constructor(
|
|
840
|
+
/**
|
|
841
|
+
The position of the target node.
|
|
842
|
+
*/
|
|
843
|
+
pos,
|
|
844
|
+
/**
|
|
845
|
+
The mark to remove.
|
|
846
|
+
*/
|
|
847
|
+
mark) {
|
|
848
|
+
super();
|
|
849
|
+
this.pos = pos;
|
|
850
|
+
this.mark = mark;
|
|
851
|
+
}
|
|
852
|
+
apply(doc) {
|
|
853
|
+
let node = doc.nodeAt(this.pos);
|
|
854
|
+
if (!node)
|
|
855
|
+
return StepResult.fail("No node at mark step's position");
|
|
856
|
+
let updated = node.type.create(node.attrs, null, this.mark.removeFromSet(node.marks));
|
|
857
|
+
return StepResult.fromReplace(doc, this.pos, this.pos + 1, new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(updated), 0, node.isLeaf ? 0 : 1));
|
|
858
|
+
}
|
|
859
|
+
invert(doc) {
|
|
860
|
+
let node = doc.nodeAt(this.pos);
|
|
861
|
+
if (!node || !this.mark.isInSet(node.marks))
|
|
862
|
+
return this;
|
|
863
|
+
return new AddNodeMarkStep(this.pos, this.mark);
|
|
864
|
+
}
|
|
865
|
+
map(mapping) {
|
|
866
|
+
let pos = mapping.mapResult(this.pos, 1);
|
|
867
|
+
return pos.deletedAfter ? null : new RemoveNodeMarkStep(pos.pos, this.mark);
|
|
868
|
+
}
|
|
869
|
+
toJSON() {
|
|
870
|
+
return { stepType: "removeNodeMark", pos: this.pos, mark: this.mark.toJSON() };
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
@internal
|
|
874
|
+
*/
|
|
875
|
+
static fromJSON(schema, json) {
|
|
876
|
+
if (typeof json.pos != "number")
|
|
877
|
+
throw new RangeError("Invalid input for RemoveNodeMarkStep.fromJSON");
|
|
878
|
+
return new RemoveNodeMarkStep(json.pos, schema.markFromJSON(json.mark));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
Step.jsonID("removeNodeMark", RemoveNodeMarkStep);
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
Replace a part of the document with a slice of new content.
|
|
885
|
+
*/
|
|
886
|
+
class ReplaceStep extends Step {
|
|
887
|
+
/**
|
|
888
|
+
The given `slice` should fit the 'gap' between `from` and
|
|
889
|
+
`to`—the depths must line up, and the surrounding nodes must be
|
|
890
|
+
able to be joined with the open sides of the slice. When
|
|
891
|
+
`structure` is true, the step will fail if the content between
|
|
892
|
+
from and to is not just a sequence of closing and then opening
|
|
893
|
+
tokens (this is to guard against rebased replace steps
|
|
894
|
+
overwriting something they weren't supposed to).
|
|
895
|
+
*/
|
|
896
|
+
constructor(
|
|
897
|
+
/**
|
|
898
|
+
The start position of the replaced range.
|
|
899
|
+
*/
|
|
900
|
+
from,
|
|
901
|
+
/**
|
|
902
|
+
The end position of the replaced range.
|
|
903
|
+
*/
|
|
904
|
+
to,
|
|
905
|
+
/**
|
|
906
|
+
The slice to insert.
|
|
907
|
+
*/
|
|
908
|
+
slice,
|
|
909
|
+
/**
|
|
910
|
+
@internal
|
|
911
|
+
*/
|
|
912
|
+
structure = false) {
|
|
913
|
+
super();
|
|
914
|
+
this.from = from;
|
|
915
|
+
this.to = to;
|
|
916
|
+
this.slice = slice;
|
|
917
|
+
this.structure = structure;
|
|
918
|
+
}
|
|
919
|
+
apply(doc) {
|
|
920
|
+
if (this.structure && contentBetween(doc, this.from, this.to))
|
|
921
|
+
return StepResult.fail("Structure replace would overwrite content");
|
|
922
|
+
return StepResult.fromReplace(doc, this.from, this.to, this.slice);
|
|
923
|
+
}
|
|
924
|
+
getMap() {
|
|
925
|
+
return new StepMap([this.from, this.to - this.from, this.slice.size]);
|
|
926
|
+
}
|
|
927
|
+
invert(doc) {
|
|
928
|
+
return new ReplaceStep(this.from, this.from + this.slice.size, doc.slice(this.from, this.to));
|
|
929
|
+
}
|
|
930
|
+
map(mapping) {
|
|
931
|
+
let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
|
|
932
|
+
if (from.deletedAcross && to.deletedAcross)
|
|
933
|
+
return null;
|
|
934
|
+
return new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice);
|
|
935
|
+
}
|
|
936
|
+
merge(other) {
|
|
937
|
+
if (!(other instanceof ReplaceStep) || other.structure || this.structure)
|
|
938
|
+
return null;
|
|
939
|
+
if (this.from + this.slice.size == other.from && !this.slice.openEnd && !other.slice.openStart) {
|
|
940
|
+
let slice = this.slice.size + other.slice.size == 0 ? prosemirrorModel.Slice.empty
|
|
941
|
+
: new prosemirrorModel.Slice(this.slice.content.append(other.slice.content), this.slice.openStart, other.slice.openEnd);
|
|
942
|
+
return new ReplaceStep(this.from, this.to + (other.to - other.from), slice, this.structure);
|
|
943
|
+
}
|
|
944
|
+
else if (other.to == this.from && !this.slice.openStart && !other.slice.openEnd) {
|
|
945
|
+
let slice = this.slice.size + other.slice.size == 0 ? prosemirrorModel.Slice.empty
|
|
946
|
+
: new prosemirrorModel.Slice(other.slice.content.append(this.slice.content), other.slice.openStart, this.slice.openEnd);
|
|
947
|
+
return new ReplaceStep(other.from, this.to, slice, this.structure);
|
|
948
|
+
}
|
|
949
|
+
else {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
toJSON() {
|
|
954
|
+
let json = { stepType: "replace", from: this.from, to: this.to };
|
|
955
|
+
if (this.slice.size)
|
|
956
|
+
json.slice = this.slice.toJSON();
|
|
957
|
+
if (this.structure)
|
|
958
|
+
json.structure = true;
|
|
959
|
+
return json;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
@internal
|
|
963
|
+
*/
|
|
964
|
+
static fromJSON(schema, json) {
|
|
965
|
+
if (typeof json.from != "number" || typeof json.to != "number")
|
|
966
|
+
throw new RangeError("Invalid input for ReplaceStep.fromJSON");
|
|
967
|
+
return new ReplaceStep(json.from, json.to, prosemirrorModel.Slice.fromJSON(schema, json.slice), !!json.structure);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
Step.jsonID("replace", ReplaceStep);
|
|
971
|
+
/**
|
|
972
|
+
Replace a part of the document with a slice of content, but
|
|
973
|
+
preserve a range of the replaced content by moving it into the
|
|
974
|
+
slice.
|
|
975
|
+
*/
|
|
976
|
+
class ReplaceAroundStep extends Step {
|
|
977
|
+
/**
|
|
978
|
+
Create a replace-around step with the given range and gap.
|
|
979
|
+
`insert` should be the point in the slice into which the content
|
|
980
|
+
of the gap should be moved. `structure` has the same meaning as
|
|
981
|
+
it has in the [`ReplaceStep`](https://prosemirror.net/docs/ref/#transform.ReplaceStep) class.
|
|
982
|
+
*/
|
|
983
|
+
constructor(
|
|
984
|
+
/**
|
|
985
|
+
The start position of the replaced range.
|
|
986
|
+
*/
|
|
987
|
+
from,
|
|
988
|
+
/**
|
|
989
|
+
The end position of the replaced range.
|
|
990
|
+
*/
|
|
991
|
+
to,
|
|
992
|
+
/**
|
|
993
|
+
The start of preserved range.
|
|
994
|
+
*/
|
|
995
|
+
gapFrom,
|
|
996
|
+
/**
|
|
997
|
+
The end of preserved range.
|
|
998
|
+
*/
|
|
999
|
+
gapTo,
|
|
1000
|
+
/**
|
|
1001
|
+
The slice to insert.
|
|
1002
|
+
*/
|
|
1003
|
+
slice,
|
|
1004
|
+
/**
|
|
1005
|
+
The position in the slice where the preserved range should be
|
|
1006
|
+
inserted.
|
|
1007
|
+
*/
|
|
1008
|
+
insert,
|
|
1009
|
+
/**
|
|
1010
|
+
@internal
|
|
1011
|
+
*/
|
|
1012
|
+
structure = false) {
|
|
1013
|
+
super();
|
|
1014
|
+
this.from = from;
|
|
1015
|
+
this.to = to;
|
|
1016
|
+
this.gapFrom = gapFrom;
|
|
1017
|
+
this.gapTo = gapTo;
|
|
1018
|
+
this.slice = slice;
|
|
1019
|
+
this.insert = insert;
|
|
1020
|
+
this.structure = structure;
|
|
1021
|
+
}
|
|
1022
|
+
apply(doc) {
|
|
1023
|
+
if (this.structure && (contentBetween(doc, this.from, this.gapFrom) ||
|
|
1024
|
+
contentBetween(doc, this.gapTo, this.to)))
|
|
1025
|
+
return StepResult.fail("Structure gap-replace would overwrite content");
|
|
1026
|
+
let gap = doc.slice(this.gapFrom, this.gapTo);
|
|
1027
|
+
if (gap.openStart || gap.openEnd)
|
|
1028
|
+
return StepResult.fail("Gap is not a flat range");
|
|
1029
|
+
let inserted = this.slice.insertAt(this.insert, gap.content);
|
|
1030
|
+
if (!inserted)
|
|
1031
|
+
return StepResult.fail("Content does not fit in gap");
|
|
1032
|
+
return StepResult.fromReplace(doc, this.from, this.to, inserted);
|
|
1033
|
+
}
|
|
1034
|
+
getMap() {
|
|
1035
|
+
return new StepMap([this.from, this.gapFrom - this.from, this.insert,
|
|
1036
|
+
this.gapTo, this.to - this.gapTo, this.slice.size - this.insert]);
|
|
1037
|
+
}
|
|
1038
|
+
invert(doc) {
|
|
1039
|
+
let gap = this.gapTo - this.gapFrom;
|
|
1040
|
+
return new ReplaceAroundStep(this.from, this.from + this.slice.size + gap, this.from + this.insert, this.from + this.insert + gap, doc.slice(this.from, this.to).removeBetween(this.gapFrom - this.from, this.gapTo - this.from), this.gapFrom - this.from, this.structure);
|
|
1041
|
+
}
|
|
1042
|
+
map(mapping) {
|
|
1043
|
+
let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
|
|
1044
|
+
let gapFrom = mapping.map(this.gapFrom, -1), gapTo = mapping.map(this.gapTo, 1);
|
|
1045
|
+
if ((from.deletedAcross && to.deletedAcross) || gapFrom < from.pos || gapTo > to.pos)
|
|
1046
|
+
return null;
|
|
1047
|
+
return new ReplaceAroundStep(from.pos, to.pos, gapFrom, gapTo, this.slice, this.insert, this.structure);
|
|
1048
|
+
}
|
|
1049
|
+
toJSON() {
|
|
1050
|
+
let json = { stepType: "replaceAround", from: this.from, to: this.to,
|
|
1051
|
+
gapFrom: this.gapFrom, gapTo: this.gapTo, insert: this.insert };
|
|
1052
|
+
if (this.slice.size)
|
|
1053
|
+
json.slice = this.slice.toJSON();
|
|
1054
|
+
if (this.structure)
|
|
1055
|
+
json.structure = true;
|
|
1056
|
+
return json;
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
@internal
|
|
1060
|
+
*/
|
|
1061
|
+
static fromJSON(schema, json) {
|
|
1062
|
+
if (typeof json.from != "number" || typeof json.to != "number" ||
|
|
1063
|
+
typeof json.gapFrom != "number" || typeof json.gapTo != "number" || typeof json.insert != "number")
|
|
1064
|
+
throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON");
|
|
1065
|
+
return new ReplaceAroundStep(json.from, json.to, json.gapFrom, json.gapTo, prosemirrorModel.Slice.fromJSON(schema, json.slice), json.insert, !!json.structure);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
Step.jsonID("replaceAround", ReplaceAroundStep);
|
|
1069
|
+
function contentBetween(doc, from, to) {
|
|
1070
|
+
let $from = doc.resolve(from), dist = to - from, depth = $from.depth;
|
|
1071
|
+
while (dist > 0 && depth > 0 && $from.indexAfter(depth) == $from.node(depth).childCount) {
|
|
1072
|
+
depth--;
|
|
1073
|
+
dist--;
|
|
1074
|
+
}
|
|
1075
|
+
if (dist > 0) {
|
|
1076
|
+
let next = $from.node(depth).maybeChild($from.indexAfter(depth));
|
|
1077
|
+
while (dist > 0) {
|
|
1078
|
+
if (!next || next.isLeaf)
|
|
1079
|
+
return true;
|
|
1080
|
+
next = next.firstChild;
|
|
1081
|
+
dist--;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function addMark(tr, from, to, mark) {
|
|
1088
|
+
let removed = [], added = [];
|
|
1089
|
+
let removing, adding;
|
|
1090
|
+
tr.doc.nodesBetween(from, to, (node, pos, parent) => {
|
|
1091
|
+
if (!node.isInline)
|
|
1092
|
+
return;
|
|
1093
|
+
let marks = node.marks;
|
|
1094
|
+
if (!mark.isInSet(marks) && parent.type.allowsMarkType(mark.type)) {
|
|
1095
|
+
let start = Math.max(pos, from), end = Math.min(pos + node.nodeSize, to);
|
|
1096
|
+
let newSet = mark.addToSet(marks);
|
|
1097
|
+
for (let i = 0; i < marks.length; i++) {
|
|
1098
|
+
if (!marks[i].isInSet(newSet)) {
|
|
1099
|
+
if (removing && removing.to == start && removing.mark.eq(marks[i]))
|
|
1100
|
+
removing.to = end;
|
|
1101
|
+
else
|
|
1102
|
+
removed.push(removing = new RemoveMarkStep(start, end, marks[i]));
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
if (adding && adding.to == start)
|
|
1106
|
+
adding.to = end;
|
|
1107
|
+
else
|
|
1108
|
+
added.push(adding = new AddMarkStep(start, end, mark));
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
removed.forEach(s => tr.step(s));
|
|
1112
|
+
added.forEach(s => tr.step(s));
|
|
1113
|
+
}
|
|
1114
|
+
function removeMark(tr, from, to, mark) {
|
|
1115
|
+
let matched = [], step = 0;
|
|
1116
|
+
tr.doc.nodesBetween(from, to, (node, pos) => {
|
|
1117
|
+
if (!node.isInline)
|
|
1118
|
+
return;
|
|
1119
|
+
step++;
|
|
1120
|
+
let toRemove = null;
|
|
1121
|
+
if (mark instanceof prosemirrorModel.MarkType) {
|
|
1122
|
+
let set = node.marks, found;
|
|
1123
|
+
while (found = mark.isInSet(set)) {
|
|
1124
|
+
(toRemove || (toRemove = [])).push(found);
|
|
1125
|
+
set = found.removeFromSet(set);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
else if (mark) {
|
|
1129
|
+
if (mark.isInSet(node.marks))
|
|
1130
|
+
toRemove = [mark];
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
toRemove = node.marks;
|
|
1134
|
+
}
|
|
1135
|
+
if (toRemove && toRemove.length) {
|
|
1136
|
+
let end = Math.min(pos + node.nodeSize, to);
|
|
1137
|
+
for (let i = 0; i < toRemove.length; i++) {
|
|
1138
|
+
let style = toRemove[i], found;
|
|
1139
|
+
for (let j = 0; j < matched.length; j++) {
|
|
1140
|
+
let m = matched[j];
|
|
1141
|
+
if (m.step == step - 1 && style.eq(matched[j].style))
|
|
1142
|
+
found = m;
|
|
1143
|
+
}
|
|
1144
|
+
if (found) {
|
|
1145
|
+
found.to = end;
|
|
1146
|
+
found.step = step;
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1149
|
+
matched.push({ style, from: Math.max(pos, from), to: end, step });
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
matched.forEach(m => tr.step(new RemoveMarkStep(m.from, m.to, m.style)));
|
|
1155
|
+
}
|
|
1156
|
+
function clearIncompatible(tr, pos, parentType, match = parentType.contentMatch) {
|
|
1157
|
+
let node = tr.doc.nodeAt(pos);
|
|
1158
|
+
let delSteps = [], cur = pos + 1;
|
|
1159
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
1160
|
+
let child = node.child(i), end = cur + child.nodeSize;
|
|
1161
|
+
let allowed = match.matchType(child.type);
|
|
1162
|
+
if (!allowed) {
|
|
1163
|
+
delSteps.push(new ReplaceStep(cur, end, prosemirrorModel.Slice.empty));
|
|
1164
|
+
}
|
|
1165
|
+
else {
|
|
1166
|
+
match = allowed;
|
|
1167
|
+
for (let j = 0; j < child.marks.length; j++)
|
|
1168
|
+
if (!parentType.allowsMarkType(child.marks[j].type))
|
|
1169
|
+
tr.step(new RemoveMarkStep(cur, end, child.marks[j]));
|
|
1170
|
+
}
|
|
1171
|
+
cur = end;
|
|
1172
|
+
}
|
|
1173
|
+
if (!match.validEnd) {
|
|
1174
|
+
let fill = match.fillBefore(prosemirrorModel.Fragment.empty, true);
|
|
1175
|
+
tr.replace(cur, cur, new prosemirrorModel.Slice(fill, 0, 0));
|
|
1176
|
+
}
|
|
1177
|
+
for (let i = delSteps.length - 1; i >= 0; i--)
|
|
1178
|
+
tr.step(delSteps[i]);
|
|
1179
|
+
}
|
|
1180
|
+
function lift(tr, range, target) {
|
|
1181
|
+
let { $from, $to, depth } = range;
|
|
1182
|
+
let gapStart = $from.before(depth + 1), gapEnd = $to.after(depth + 1);
|
|
1183
|
+
let start = gapStart, end = gapEnd;
|
|
1184
|
+
let before = prosemirrorModel.Fragment.empty, openStart = 0;
|
|
1185
|
+
for (let d = depth, splitting = false; d > target; d--)
|
|
1186
|
+
if (splitting || $from.index(d) > 0) {
|
|
1187
|
+
splitting = true;
|
|
1188
|
+
before = prosemirrorModel.Fragment.from($from.node(d).copy(before));
|
|
1189
|
+
openStart++;
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
start--;
|
|
1193
|
+
}
|
|
1194
|
+
let after = prosemirrorModel.Fragment.empty, openEnd = 0;
|
|
1195
|
+
for (let d = depth, splitting = false; d > target; d--)
|
|
1196
|
+
if (splitting || $to.after(d + 1) < $to.end(d)) {
|
|
1197
|
+
splitting = true;
|
|
1198
|
+
after = prosemirrorModel.Fragment.from($to.node(d).copy(after));
|
|
1199
|
+
openEnd++;
|
|
1200
|
+
}
|
|
1201
|
+
else {
|
|
1202
|
+
end++;
|
|
1203
|
+
}
|
|
1204
|
+
tr.step(new ReplaceAroundStep(start, end, gapStart, gapEnd, new prosemirrorModel.Slice(before.append(after), openStart, openEnd), before.size - openStart, true));
|
|
1205
|
+
}
|
|
1206
|
+
function wrap(tr, range, wrappers) {
|
|
1207
|
+
let content = prosemirrorModel.Fragment.empty;
|
|
1208
|
+
for (let i = wrappers.length - 1; i >= 0; i--) {
|
|
1209
|
+
if (content.size) {
|
|
1210
|
+
let match = wrappers[i].type.contentMatch.matchFragment(content);
|
|
1211
|
+
if (!match || !match.validEnd)
|
|
1212
|
+
throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper");
|
|
1213
|
+
}
|
|
1214
|
+
content = prosemirrorModel.Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content));
|
|
1215
|
+
}
|
|
1216
|
+
let start = range.start, end = range.end;
|
|
1217
|
+
tr.step(new ReplaceAroundStep(start, end, start, end, new prosemirrorModel.Slice(content, 0, 0), wrappers.length, true));
|
|
1218
|
+
}
|
|
1219
|
+
function setBlockType(tr, from, to, type, attrs) {
|
|
1220
|
+
if (!type.isTextblock)
|
|
1221
|
+
throw new RangeError("Type given to setBlockType should be a textblock");
|
|
1222
|
+
let mapFrom = tr.steps.length;
|
|
1223
|
+
tr.doc.nodesBetween(from, to, (node, pos) => {
|
|
1224
|
+
if (node.isTextblock && !node.hasMarkup(type, attrs) && canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
|
|
1225
|
+
// Ensure all markup that isn't allowed in the new node type is cleared
|
|
1226
|
+
tr.clearIncompatible(tr.mapping.slice(mapFrom).map(pos, 1), type);
|
|
1227
|
+
let mapping = tr.mapping.slice(mapFrom);
|
|
1228
|
+
let startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1);
|
|
1229
|
+
tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1, new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(type.create(attrs, null, node.marks)), 0, 0), 1, true));
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
function canChangeType(doc, pos, type) {
|
|
1235
|
+
let $pos = doc.resolve(pos), index = $pos.index();
|
|
1236
|
+
return $pos.parent.canReplaceWith(index, index + 1, type);
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
Change the type, attributes, and/or marks of the node at `pos`.
|
|
1240
|
+
When `type` isn't given, the existing node type is preserved,
|
|
1241
|
+
*/
|
|
1242
|
+
function setNodeMarkup(tr, pos, type, attrs, marks) {
|
|
1243
|
+
let node = tr.doc.nodeAt(pos);
|
|
1244
|
+
if (!node)
|
|
1245
|
+
throw new RangeError("No node at given position");
|
|
1246
|
+
if (!type)
|
|
1247
|
+
type = node.type;
|
|
1248
|
+
let newNode = type.create(attrs, null, marks || node.marks);
|
|
1249
|
+
if (node.isLeaf)
|
|
1250
|
+
return tr.replaceWith(pos, pos + node.nodeSize, newNode);
|
|
1251
|
+
if (!type.validContent(node.content))
|
|
1252
|
+
throw new RangeError("Invalid content for node type " + type.name);
|
|
1253
|
+
tr.step(new ReplaceAroundStep(pos, pos + node.nodeSize, pos + 1, pos + node.nodeSize - 1, new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(newNode), 0, 0), 1, true));
|
|
1254
|
+
}
|
|
1255
|
+
function split(tr, pos, depth = 1, typesAfter) {
|
|
1256
|
+
let $pos = tr.doc.resolve(pos), before = prosemirrorModel.Fragment.empty, after = prosemirrorModel.Fragment.empty;
|
|
1257
|
+
for (let d = $pos.depth, e = $pos.depth - depth, i = depth - 1; d > e; d--, i--) {
|
|
1258
|
+
before = prosemirrorModel.Fragment.from($pos.node(d).copy(before));
|
|
1259
|
+
let typeAfter = typesAfter && typesAfter[i];
|
|
1260
|
+
after = prosemirrorModel.Fragment.from(typeAfter ? typeAfter.type.create(typeAfter.attrs, after) : $pos.node(d).copy(after));
|
|
1261
|
+
}
|
|
1262
|
+
tr.step(new ReplaceStep(pos, pos, new prosemirrorModel.Slice(before.append(after), depth, depth), true));
|
|
1263
|
+
}
|
|
1264
|
+
function join(tr, pos, depth) {
|
|
1265
|
+
let step = new ReplaceStep(pos - depth, pos + depth, prosemirrorModel.Slice.empty, true);
|
|
1266
|
+
tr.step(step);
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
Try to find a point where a node of the given type can be inserted
|
|
1270
|
+
near `pos`, by searching up the node hierarchy when `pos` itself
|
|
1271
|
+
isn't a valid place but is at the start or end of a node. Return
|
|
1272
|
+
null if no position was found.
|
|
1273
|
+
*/
|
|
1274
|
+
function insertPoint(doc, pos, nodeType) {
|
|
1275
|
+
let $pos = doc.resolve(pos);
|
|
1276
|
+
if ($pos.parent.canReplaceWith($pos.index(), $pos.index(), nodeType))
|
|
1277
|
+
return pos;
|
|
1278
|
+
if ($pos.parentOffset == 0)
|
|
1279
|
+
for (let d = $pos.depth - 1; d >= 0; d--) {
|
|
1280
|
+
let index = $pos.index(d);
|
|
1281
|
+
if ($pos.node(d).canReplaceWith(index, index, nodeType))
|
|
1282
|
+
return $pos.before(d + 1);
|
|
1283
|
+
if (index > 0)
|
|
1284
|
+
return null;
|
|
1285
|
+
}
|
|
1286
|
+
if ($pos.parentOffset == $pos.parent.content.size)
|
|
1287
|
+
for (let d = $pos.depth - 1; d >= 0; d--) {
|
|
1288
|
+
let index = $pos.indexAfter(d);
|
|
1289
|
+
if ($pos.node(d).canReplaceWith(index, index, nodeType))
|
|
1290
|
+
return $pos.after(d + 1);
|
|
1291
|
+
if (index < $pos.node(d).childCount)
|
|
1292
|
+
return null;
|
|
1293
|
+
}
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/**
|
|
1298
|
+
‘Fit’ a slice into a given position in the document, producing a
|
|
1299
|
+
[step](https://prosemirror.net/docs/ref/#transform.Step) that inserts it. Will return null if
|
|
1300
|
+
there's no meaningful way to insert the slice here, or inserting it
|
|
1301
|
+
would be a no-op (an empty slice over an empty range).
|
|
1302
|
+
*/
|
|
1303
|
+
function replaceStep(doc, from, to = from, slice = prosemirrorModel.Slice.empty) {
|
|
1304
|
+
if (from == to && !slice.size)
|
|
1305
|
+
return null;
|
|
1306
|
+
let $from = doc.resolve(from), $to = doc.resolve(to);
|
|
1307
|
+
// Optimization -- avoid work if it's obvious that it's not needed.
|
|
1308
|
+
if (fitsTrivially($from, $to, slice))
|
|
1309
|
+
return new ReplaceStep(from, to, slice);
|
|
1310
|
+
return new Fitter($from, $to, slice).fit();
|
|
1311
|
+
}
|
|
1312
|
+
function fitsTrivially($from, $to, slice) {
|
|
1313
|
+
return !slice.openStart && !slice.openEnd && $from.start() == $to.start() &&
|
|
1314
|
+
$from.parent.canReplace($from.index(), $to.index(), slice.content);
|
|
1315
|
+
}
|
|
1316
|
+
// Algorithm for 'placing' the elements of a slice into a gap:
|
|
1317
|
+
//
|
|
1318
|
+
// We consider the content of each node that is open to the left to be
|
|
1319
|
+
// independently placeable. I.e. in <p("foo"), p("bar")>, when the
|
|
1320
|
+
// paragraph on the left is open, "foo" can be placed (somewhere on
|
|
1321
|
+
// the left side of the replacement gap) independently from p("bar").
|
|
1322
|
+
//
|
|
1323
|
+
// This class tracks the state of the placement progress in the
|
|
1324
|
+
// following properties:
|
|
1325
|
+
//
|
|
1326
|
+
// - `frontier` holds a stack of `{type, match}` objects that
|
|
1327
|
+
// represent the open side of the replacement. It starts at
|
|
1328
|
+
// `$from`, then moves forward as content is placed, and is finally
|
|
1329
|
+
// reconciled with `$to`.
|
|
1330
|
+
//
|
|
1331
|
+
// - `unplaced` is a slice that represents the content that hasn't
|
|
1332
|
+
// been placed yet.
|
|
1333
|
+
//
|
|
1334
|
+
// - `placed` is a fragment of placed content. Its open-start value
|
|
1335
|
+
// is implicit in `$from`, and its open-end value in `frontier`.
|
|
1336
|
+
class Fitter {
|
|
1337
|
+
constructor($from, $to, unplaced) {
|
|
1338
|
+
this.$from = $from;
|
|
1339
|
+
this.$to = $to;
|
|
1340
|
+
this.unplaced = unplaced;
|
|
1341
|
+
this.frontier = [];
|
|
1342
|
+
this.placed = prosemirrorModel.Fragment.empty;
|
|
1343
|
+
for (let i = 0; i <= $from.depth; i++) {
|
|
1344
|
+
let node = $from.node(i);
|
|
1345
|
+
this.frontier.push({
|
|
1346
|
+
type: node.type,
|
|
1347
|
+
match: node.contentMatchAt($from.indexAfter(i))
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
for (let i = $from.depth; i > 0; i--)
|
|
1351
|
+
this.placed = prosemirrorModel.Fragment.from($from.node(i).copy(this.placed));
|
|
1352
|
+
}
|
|
1353
|
+
get depth() { return this.frontier.length - 1; }
|
|
1354
|
+
fit() {
|
|
1355
|
+
// As long as there's unplaced content, try to place some of it.
|
|
1356
|
+
// If that fails, either increase the open score of the unplaced
|
|
1357
|
+
// slice, or drop nodes from it, and then try again.
|
|
1358
|
+
while (this.unplaced.size) {
|
|
1359
|
+
let fit = this.findFittable();
|
|
1360
|
+
if (fit)
|
|
1361
|
+
this.placeNodes(fit);
|
|
1362
|
+
else
|
|
1363
|
+
this.openMore() || this.dropNode();
|
|
1364
|
+
}
|
|
1365
|
+
// When there's inline content directly after the frontier _and_
|
|
1366
|
+
// directly after `this.$to`, we must generate a `ReplaceAround`
|
|
1367
|
+
// step that pulls that content into the node after the frontier.
|
|
1368
|
+
// That means the fitting must be done to the end of the textblock
|
|
1369
|
+
// node after `this.$to`, not `this.$to` itself.
|
|
1370
|
+
let moveInline = this.mustMoveInline(), placedSize = this.placed.size - this.depth - this.$from.depth;
|
|
1371
|
+
let $from = this.$from, $to = this.close(moveInline < 0 ? this.$to : $from.doc.resolve(moveInline));
|
|
1372
|
+
if (!$to)
|
|
1373
|
+
return null;
|
|
1374
|
+
// If closing to `$to` succeeded, create a step
|
|
1375
|
+
let content = this.placed, openStart = $from.depth, openEnd = $to.depth;
|
|
1376
|
+
while (openStart && openEnd && content.childCount == 1) { // Normalize by dropping open parent nodes
|
|
1377
|
+
content = content.firstChild.content;
|
|
1378
|
+
openStart--;
|
|
1379
|
+
openEnd--;
|
|
1380
|
+
}
|
|
1381
|
+
let slice = new prosemirrorModel.Slice(content, openStart, openEnd);
|
|
1382
|
+
if (moveInline > -1)
|
|
1383
|
+
return new ReplaceAroundStep($from.pos, moveInline, this.$to.pos, this.$to.end(), slice, placedSize);
|
|
1384
|
+
if (slice.size || $from.pos != this.$to.pos) // Don't generate no-op steps
|
|
1385
|
+
return new ReplaceStep($from.pos, $to.pos, slice);
|
|
1386
|
+
return null;
|
|
1387
|
+
}
|
|
1388
|
+
// Find a position on the start spine of `this.unplaced` that has
|
|
1389
|
+
// content that can be moved somewhere on the frontier. Returns two
|
|
1390
|
+
// depths, one for the slice and one for the frontier.
|
|
1391
|
+
findFittable() {
|
|
1392
|
+
// Only try wrapping nodes (pass 2) after finding a place without
|
|
1393
|
+
// wrapping failed.
|
|
1394
|
+
for (let pass = 1; pass <= 2; pass++) {
|
|
1395
|
+
for (let sliceDepth = this.unplaced.openStart; sliceDepth >= 0; sliceDepth--) {
|
|
1396
|
+
let fragment, parent = null;
|
|
1397
|
+
if (sliceDepth) {
|
|
1398
|
+
parent = contentAt(this.unplaced.content, sliceDepth - 1).firstChild;
|
|
1399
|
+
fragment = parent.content;
|
|
1400
|
+
}
|
|
1401
|
+
else {
|
|
1402
|
+
fragment = this.unplaced.content;
|
|
1403
|
+
}
|
|
1404
|
+
let first = fragment.firstChild;
|
|
1405
|
+
for (let frontierDepth = this.depth; frontierDepth >= 0; frontierDepth--) {
|
|
1406
|
+
let { type, match } = this.frontier[frontierDepth], wrap, inject = null;
|
|
1407
|
+
// In pass 1, if the next node matches, or there is no next
|
|
1408
|
+
// node but the parents look compatible, we've found a
|
|
1409
|
+
// place.
|
|
1410
|
+
if (pass == 1 && (first ? match.matchType(first.type) || (inject = match.fillBefore(prosemirrorModel.Fragment.from(first), false))
|
|
1411
|
+
: parent && type.compatibleContent(parent.type)))
|
|
1412
|
+
return { sliceDepth, frontierDepth, parent, inject };
|
|
1413
|
+
// In pass 2, look for a set of wrapping nodes that make
|
|
1414
|
+
// `first` fit here.
|
|
1415
|
+
else if (pass == 2 && first && (wrap = match.findWrapping(first.type)))
|
|
1416
|
+
return { sliceDepth, frontierDepth, parent, wrap };
|
|
1417
|
+
// Don't continue looking further up if the parent node
|
|
1418
|
+
// would fit here.
|
|
1419
|
+
if (parent && match.matchType(parent.type))
|
|
1420
|
+
break;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
openMore() {
|
|
1426
|
+
let { content, openStart, openEnd } = this.unplaced;
|
|
1427
|
+
let inner = contentAt(content, openStart);
|
|
1428
|
+
if (!inner.childCount || inner.firstChild.isLeaf)
|
|
1429
|
+
return false;
|
|
1430
|
+
this.unplaced = new prosemirrorModel.Slice(content, openStart + 1, Math.max(openEnd, inner.size + openStart >= content.size - openEnd ? openStart + 1 : 0));
|
|
1431
|
+
return true;
|
|
1432
|
+
}
|
|
1433
|
+
dropNode() {
|
|
1434
|
+
let { content, openStart, openEnd } = this.unplaced;
|
|
1435
|
+
let inner = contentAt(content, openStart);
|
|
1436
|
+
if (inner.childCount <= 1 && openStart > 0) {
|
|
1437
|
+
let openAtEnd = content.size - openStart <= openStart + inner.size;
|
|
1438
|
+
this.unplaced = new prosemirrorModel.Slice(dropFromFragment(content, openStart - 1, 1), openStart - 1, openAtEnd ? openStart - 1 : openEnd);
|
|
1439
|
+
}
|
|
1440
|
+
else {
|
|
1441
|
+
this.unplaced = new prosemirrorModel.Slice(dropFromFragment(content, openStart, 1), openStart, openEnd);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
// Move content from the unplaced slice at `sliceDepth` to the
|
|
1445
|
+
// frontier node at `frontierDepth`. Close that frontier node when
|
|
1446
|
+
// applicable.
|
|
1447
|
+
placeNodes({ sliceDepth, frontierDepth, parent, inject, wrap }) {
|
|
1448
|
+
while (this.depth > frontierDepth)
|
|
1449
|
+
this.closeFrontierNode();
|
|
1450
|
+
if (wrap)
|
|
1451
|
+
for (let i = 0; i < wrap.length; i++)
|
|
1452
|
+
this.openFrontierNode(wrap[i]);
|
|
1453
|
+
let slice = this.unplaced, fragment = parent ? parent.content : slice.content;
|
|
1454
|
+
let openStart = slice.openStart - sliceDepth;
|
|
1455
|
+
let taken = 0, add = [];
|
|
1456
|
+
let { match, type } = this.frontier[frontierDepth];
|
|
1457
|
+
if (inject) {
|
|
1458
|
+
for (let i = 0; i < inject.childCount; i++)
|
|
1459
|
+
add.push(inject.child(i));
|
|
1460
|
+
match = match.matchFragment(inject);
|
|
1461
|
+
}
|
|
1462
|
+
// Computes the amount of (end) open nodes at the end of the
|
|
1463
|
+
// fragment. When 0, the parent is open, but no more. When
|
|
1464
|
+
// negative, nothing is open.
|
|
1465
|
+
let openEndCount = (fragment.size + sliceDepth) - (slice.content.size - slice.openEnd);
|
|
1466
|
+
// Scan over the fragment, fitting as many child nodes as
|
|
1467
|
+
// possible.
|
|
1468
|
+
while (taken < fragment.childCount) {
|
|
1469
|
+
let next = fragment.child(taken), matches = match.matchType(next.type);
|
|
1470
|
+
if (!matches)
|
|
1471
|
+
break;
|
|
1472
|
+
taken++;
|
|
1473
|
+
if (taken > 1 || openStart == 0 || next.content.size) { // Drop empty open nodes
|
|
1474
|
+
match = matches;
|
|
1475
|
+
add.push(closeNodeStart(next.mark(type.allowedMarks(next.marks)), taken == 1 ? openStart : 0, taken == fragment.childCount ? openEndCount : -1));
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
let toEnd = taken == fragment.childCount;
|
|
1479
|
+
if (!toEnd)
|
|
1480
|
+
openEndCount = -1;
|
|
1481
|
+
this.placed = addToFragment(this.placed, frontierDepth, prosemirrorModel.Fragment.from(add));
|
|
1482
|
+
this.frontier[frontierDepth].match = match;
|
|
1483
|
+
// If the parent types match, and the entire node was moved, and
|
|
1484
|
+
// it's not open, close this frontier node right away.
|
|
1485
|
+
if (toEnd && openEndCount < 0 && parent && parent.type == this.frontier[this.depth].type && this.frontier.length > 1)
|
|
1486
|
+
this.closeFrontierNode();
|
|
1487
|
+
// Add new frontier nodes for any open nodes at the end.
|
|
1488
|
+
for (let i = 0, cur = fragment; i < openEndCount; i++) {
|
|
1489
|
+
let node = cur.lastChild;
|
|
1490
|
+
this.frontier.push({ type: node.type, match: node.contentMatchAt(node.childCount) });
|
|
1491
|
+
cur = node.content;
|
|
1492
|
+
}
|
|
1493
|
+
// Update `this.unplaced`. Drop the entire node from which we
|
|
1494
|
+
// placed it we got to its end, otherwise just drop the placed
|
|
1495
|
+
// nodes.
|
|
1496
|
+
this.unplaced = !toEnd ? new prosemirrorModel.Slice(dropFromFragment(slice.content, sliceDepth, taken), slice.openStart, slice.openEnd)
|
|
1497
|
+
: sliceDepth == 0 ? prosemirrorModel.Slice.empty
|
|
1498
|
+
: new prosemirrorModel.Slice(dropFromFragment(slice.content, sliceDepth - 1, 1), sliceDepth - 1, openEndCount < 0 ? slice.openEnd : sliceDepth - 1);
|
|
1499
|
+
}
|
|
1500
|
+
mustMoveInline() {
|
|
1501
|
+
if (!this.$to.parent.isTextblock)
|
|
1502
|
+
return -1;
|
|
1503
|
+
let top = this.frontier[this.depth], level;
|
|
1504
|
+
if (!top.type.isTextblock || !contentAfterFits(this.$to, this.$to.depth, top.type, top.match, false) ||
|
|
1505
|
+
(this.$to.depth == this.depth && (level = this.findCloseLevel(this.$to)) && level.depth == this.depth))
|
|
1506
|
+
return -1;
|
|
1507
|
+
let { depth } = this.$to, after = this.$to.after(depth);
|
|
1508
|
+
while (depth > 1 && after == this.$to.end(--depth))
|
|
1509
|
+
++after;
|
|
1510
|
+
return after;
|
|
1511
|
+
}
|
|
1512
|
+
findCloseLevel($to) {
|
|
1513
|
+
scan: for (let i = Math.min(this.depth, $to.depth); i >= 0; i--) {
|
|
1514
|
+
let { match, type } = this.frontier[i];
|
|
1515
|
+
let dropInner = i < $to.depth && $to.end(i + 1) == $to.pos + ($to.depth - (i + 1));
|
|
1516
|
+
let fit = contentAfterFits($to, i, type, match, dropInner);
|
|
1517
|
+
if (!fit)
|
|
1518
|
+
continue;
|
|
1519
|
+
for (let d = i - 1; d >= 0; d--) {
|
|
1520
|
+
let { match, type } = this.frontier[d];
|
|
1521
|
+
let matches = contentAfterFits($to, d, type, match, true);
|
|
1522
|
+
if (!matches || matches.childCount)
|
|
1523
|
+
continue scan;
|
|
1524
|
+
}
|
|
1525
|
+
return { depth: i, fit, move: dropInner ? $to.doc.resolve($to.after(i + 1)) : $to };
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
close($to) {
|
|
1529
|
+
let close = this.findCloseLevel($to);
|
|
1530
|
+
if (!close)
|
|
1531
|
+
return null;
|
|
1532
|
+
while (this.depth > close.depth)
|
|
1533
|
+
this.closeFrontierNode();
|
|
1534
|
+
if (close.fit.childCount)
|
|
1535
|
+
this.placed = addToFragment(this.placed, close.depth, close.fit);
|
|
1536
|
+
$to = close.move;
|
|
1537
|
+
for (let d = close.depth + 1; d <= $to.depth; d++) {
|
|
1538
|
+
let node = $to.node(d), add = node.type.contentMatch.fillBefore(node.content, true, $to.index(d));
|
|
1539
|
+
this.openFrontierNode(node.type, node.attrs, add);
|
|
1540
|
+
}
|
|
1541
|
+
return $to;
|
|
1542
|
+
}
|
|
1543
|
+
openFrontierNode(type, attrs = null, content) {
|
|
1544
|
+
let top = this.frontier[this.depth];
|
|
1545
|
+
top.match = top.match.matchType(type);
|
|
1546
|
+
this.placed = addToFragment(this.placed, this.depth, prosemirrorModel.Fragment.from(type.create(attrs, content)));
|
|
1547
|
+
this.frontier.push({ type, match: type.contentMatch });
|
|
1548
|
+
}
|
|
1549
|
+
closeFrontierNode() {
|
|
1550
|
+
let open = this.frontier.pop();
|
|
1551
|
+
let add = open.match.fillBefore(prosemirrorModel.Fragment.empty, true);
|
|
1552
|
+
if (add.childCount)
|
|
1553
|
+
this.placed = addToFragment(this.placed, this.frontier.length, add);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
function dropFromFragment(fragment, depth, count) {
|
|
1557
|
+
if (depth == 0)
|
|
1558
|
+
return fragment.cutByIndex(count, fragment.childCount);
|
|
1559
|
+
return fragment.replaceChild(0, fragment.firstChild.copy(dropFromFragment(fragment.firstChild.content, depth - 1, count)));
|
|
1560
|
+
}
|
|
1561
|
+
function addToFragment(fragment, depth, content) {
|
|
1562
|
+
if (depth == 0)
|
|
1563
|
+
return fragment.append(content);
|
|
1564
|
+
return fragment.replaceChild(fragment.childCount - 1, fragment.lastChild.copy(addToFragment(fragment.lastChild.content, depth - 1, content)));
|
|
1565
|
+
}
|
|
1566
|
+
function contentAt(fragment, depth) {
|
|
1567
|
+
for (let i = 0; i < depth; i++)
|
|
1568
|
+
fragment = fragment.firstChild.content;
|
|
1569
|
+
return fragment;
|
|
1570
|
+
}
|
|
1571
|
+
function closeNodeStart(node, openStart, openEnd) {
|
|
1572
|
+
if (openStart <= 0)
|
|
1573
|
+
return node;
|
|
1574
|
+
let frag = node.content;
|
|
1575
|
+
if (openStart > 1)
|
|
1576
|
+
frag = frag.replaceChild(0, closeNodeStart(frag.firstChild, openStart - 1, frag.childCount == 1 ? openEnd - 1 : 0));
|
|
1577
|
+
if (openStart > 0) {
|
|
1578
|
+
frag = node.type.contentMatch.fillBefore(frag).append(frag);
|
|
1579
|
+
if (openEnd <= 0)
|
|
1580
|
+
frag = frag.append(node.type.contentMatch.matchFragment(frag).fillBefore(prosemirrorModel.Fragment.empty, true));
|
|
1581
|
+
}
|
|
1582
|
+
return node.copy(frag);
|
|
1583
|
+
}
|
|
1584
|
+
function contentAfterFits($to, depth, type, match, open) {
|
|
1585
|
+
let node = $to.node(depth), index = open ? $to.indexAfter(depth) : $to.index(depth);
|
|
1586
|
+
if (index == node.childCount && !type.compatibleContent(node.type))
|
|
1587
|
+
return null;
|
|
1588
|
+
let fit = match.fillBefore(node.content, true, index);
|
|
1589
|
+
return fit && !invalidMarks(type, node.content, index) ? fit : null;
|
|
1590
|
+
}
|
|
1591
|
+
function invalidMarks(type, fragment, start) {
|
|
1592
|
+
for (let i = start; i < fragment.childCount; i++)
|
|
1593
|
+
if (!type.allowsMarks(fragment.child(i).marks))
|
|
1594
|
+
return true;
|
|
1595
|
+
return false;
|
|
1596
|
+
}
|
|
1597
|
+
function definesContent(type) {
|
|
1598
|
+
return type.spec.defining || type.spec.definingForContent;
|
|
1599
|
+
}
|
|
1600
|
+
function replaceRange(tr, from, to, slice) {
|
|
1601
|
+
if (!slice.size)
|
|
1602
|
+
return tr.deleteRange(from, to);
|
|
1603
|
+
let $from = tr.doc.resolve(from), $to = tr.doc.resolve(to);
|
|
1604
|
+
if (fitsTrivially($from, $to, slice))
|
|
1605
|
+
return tr.step(new ReplaceStep(from, to, slice));
|
|
1606
|
+
let targetDepths = coveredDepths($from, tr.doc.resolve(to));
|
|
1607
|
+
// Can't replace the whole document, so remove 0 if it's present
|
|
1608
|
+
if (targetDepths[targetDepths.length - 1] == 0)
|
|
1609
|
+
targetDepths.pop();
|
|
1610
|
+
// Negative numbers represent not expansion over the whole node at
|
|
1611
|
+
// that depth, but replacing from $from.before(-D) to $to.pos.
|
|
1612
|
+
let preferredTarget = -($from.depth + 1);
|
|
1613
|
+
targetDepths.unshift(preferredTarget);
|
|
1614
|
+
// This loop picks a preferred target depth, if one of the covering
|
|
1615
|
+
// depths is not outside of a defining node, and adds negative
|
|
1616
|
+
// depths for any depth that has $from at its start and does not
|
|
1617
|
+
// cross a defining node.
|
|
1618
|
+
for (let d = $from.depth, pos = $from.pos - 1; d > 0; d--, pos--) {
|
|
1619
|
+
let spec = $from.node(d).type.spec;
|
|
1620
|
+
if (spec.defining || spec.definingAsContext || spec.isolating)
|
|
1621
|
+
break;
|
|
1622
|
+
if (targetDepths.indexOf(d) > -1)
|
|
1623
|
+
preferredTarget = d;
|
|
1624
|
+
else if ($from.before(d) == pos)
|
|
1625
|
+
targetDepths.splice(1, 0, -d);
|
|
1626
|
+
}
|
|
1627
|
+
// Try to fit each possible depth of the slice into each possible
|
|
1628
|
+
// target depth, starting with the preferred depths.
|
|
1629
|
+
let preferredTargetIndex = targetDepths.indexOf(preferredTarget);
|
|
1630
|
+
let leftNodes = [], preferredDepth = slice.openStart;
|
|
1631
|
+
for (let content = slice.content, i = 0;; i++) {
|
|
1632
|
+
let node = content.firstChild;
|
|
1633
|
+
leftNodes.push(node);
|
|
1634
|
+
if (i == slice.openStart)
|
|
1635
|
+
break;
|
|
1636
|
+
content = node.content;
|
|
1637
|
+
}
|
|
1638
|
+
// Back up preferredDepth to cover defining textblocks directly
|
|
1639
|
+
// above it, possibly skipping a non-defining textblock.
|
|
1640
|
+
for (let d = preferredDepth - 1; d >= 0; d--) {
|
|
1641
|
+
let type = leftNodes[d].type, def = definesContent(type);
|
|
1642
|
+
if (def && $from.node(preferredTargetIndex).type != type)
|
|
1643
|
+
preferredDepth = d;
|
|
1644
|
+
else if (def || !type.isTextblock)
|
|
1645
|
+
break;
|
|
1646
|
+
}
|
|
1647
|
+
for (let j = slice.openStart; j >= 0; j--) {
|
|
1648
|
+
let openDepth = (j + preferredDepth + 1) % (slice.openStart + 1);
|
|
1649
|
+
let insert = leftNodes[openDepth];
|
|
1650
|
+
if (!insert)
|
|
1651
|
+
continue;
|
|
1652
|
+
for (let i = 0; i < targetDepths.length; i++) {
|
|
1653
|
+
// Loop over possible expansion levels, starting with the
|
|
1654
|
+
// preferred one
|
|
1655
|
+
let targetDepth = targetDepths[(i + preferredTargetIndex) % targetDepths.length], expand = true;
|
|
1656
|
+
if (targetDepth < 0) {
|
|
1657
|
+
expand = false;
|
|
1658
|
+
targetDepth = -targetDepth;
|
|
1659
|
+
}
|
|
1660
|
+
let parent = $from.node(targetDepth - 1), index = $from.index(targetDepth - 1);
|
|
1661
|
+
if (parent.canReplaceWith(index, index, insert.type, insert.marks))
|
|
1662
|
+
return tr.replace($from.before(targetDepth), expand ? $to.after(targetDepth) : to, new prosemirrorModel.Slice(closeFragment(slice.content, 0, slice.openStart, openDepth), openDepth, slice.openEnd));
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
let startSteps = tr.steps.length;
|
|
1666
|
+
for (let i = targetDepths.length - 1; i >= 0; i--) {
|
|
1667
|
+
tr.replace(from, to, slice);
|
|
1668
|
+
if (tr.steps.length > startSteps)
|
|
1669
|
+
break;
|
|
1670
|
+
let depth = targetDepths[i];
|
|
1671
|
+
if (depth < 0)
|
|
1672
|
+
continue;
|
|
1673
|
+
from = $from.before(depth);
|
|
1674
|
+
to = $to.after(depth);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
function closeFragment(fragment, depth, oldOpen, newOpen, parent) {
|
|
1678
|
+
if (depth < oldOpen) {
|
|
1679
|
+
let first = fragment.firstChild;
|
|
1680
|
+
fragment = fragment.replaceChild(0, first.copy(closeFragment(first.content, depth + 1, oldOpen, newOpen, first)));
|
|
1681
|
+
}
|
|
1682
|
+
if (depth > newOpen) {
|
|
1683
|
+
let match = parent.contentMatchAt(0);
|
|
1684
|
+
let start = match.fillBefore(fragment).append(fragment);
|
|
1685
|
+
fragment = start.append(match.matchFragment(start).fillBefore(prosemirrorModel.Fragment.empty, true));
|
|
1686
|
+
}
|
|
1687
|
+
return fragment;
|
|
1688
|
+
}
|
|
1689
|
+
function replaceRangeWith(tr, from, to, node) {
|
|
1690
|
+
if (!node.isInline && from == to && tr.doc.resolve(from).parent.content.size) {
|
|
1691
|
+
let point = insertPoint(tr.doc, from, node.type);
|
|
1692
|
+
if (point != null)
|
|
1693
|
+
from = to = point;
|
|
1694
|
+
}
|
|
1695
|
+
tr.replaceRange(from, to, new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(node), 0, 0));
|
|
1696
|
+
}
|
|
1697
|
+
function deleteRange(tr, from, to) {
|
|
1698
|
+
let $from = tr.doc.resolve(from), $to = tr.doc.resolve(to);
|
|
1699
|
+
let covered = coveredDepths($from, $to);
|
|
1700
|
+
for (let i = 0; i < covered.length; i++) {
|
|
1701
|
+
let depth = covered[i], last = i == covered.length - 1;
|
|
1702
|
+
if ((last && depth == 0) || $from.node(depth).type.contentMatch.validEnd)
|
|
1703
|
+
return tr.delete($from.start(depth), $to.end(depth));
|
|
1704
|
+
if (depth > 0 && (last || $from.node(depth - 1).canReplace($from.index(depth - 1), $to.indexAfter(depth - 1))))
|
|
1705
|
+
return tr.delete($from.before(depth), $to.after(depth));
|
|
1706
|
+
}
|
|
1707
|
+
for (let d = 1; d <= $from.depth && d <= $to.depth; d++) {
|
|
1708
|
+
if (from - $from.start(d) == $from.depth - d && to > $from.end(d) && $to.end(d) - to != $to.depth - d)
|
|
1709
|
+
return tr.delete($from.before(d), to);
|
|
1710
|
+
}
|
|
1711
|
+
tr.delete(from, to);
|
|
1712
|
+
}
|
|
1713
|
+
// Returns an array of all depths for which $from - $to spans the
|
|
1714
|
+
// whole content of the nodes at that depth.
|
|
1715
|
+
function coveredDepths($from, $to) {
|
|
1716
|
+
let result = [], minDepth = Math.min($from.depth, $to.depth);
|
|
1717
|
+
for (let d = minDepth; d >= 0; d--) {
|
|
1718
|
+
let start = $from.start(d);
|
|
1719
|
+
if (start < $from.pos - ($from.depth - d) ||
|
|
1720
|
+
$to.end(d) > $to.pos + ($to.depth - d) ||
|
|
1721
|
+
$from.node(d).type.spec.isolating ||
|
|
1722
|
+
$to.node(d).type.spec.isolating)
|
|
1723
|
+
break;
|
|
1724
|
+
if (start == $to.start(d) ||
|
|
1725
|
+
(d == $from.depth && d == $to.depth && $from.parent.inlineContent && $to.parent.inlineContent &&
|
|
1726
|
+
d && $to.start(d - 1) == start - 1))
|
|
1727
|
+
result.push(d);
|
|
1728
|
+
}
|
|
1729
|
+
return result;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
/**
|
|
1733
|
+
Update an attribute in a specific node.
|
|
1734
|
+
*/
|
|
1735
|
+
class AttrStep extends Step {
|
|
1736
|
+
/**
|
|
1737
|
+
Construct an attribute step.
|
|
1738
|
+
*/
|
|
1739
|
+
constructor(
|
|
1740
|
+
/**
|
|
1741
|
+
The position of the target node.
|
|
1742
|
+
*/
|
|
1743
|
+
pos,
|
|
1744
|
+
/**
|
|
1745
|
+
The attribute to set.
|
|
1746
|
+
*/
|
|
1747
|
+
attr,
|
|
1748
|
+
// The attribute's new value.
|
|
1749
|
+
value) {
|
|
1750
|
+
super();
|
|
1751
|
+
this.pos = pos;
|
|
1752
|
+
this.attr = attr;
|
|
1753
|
+
this.value = value;
|
|
1754
|
+
}
|
|
1755
|
+
apply(doc) {
|
|
1756
|
+
let node = doc.nodeAt(this.pos);
|
|
1757
|
+
if (!node)
|
|
1758
|
+
return StepResult.fail("No node at attribute step's position");
|
|
1759
|
+
let attrs = Object.create(null);
|
|
1760
|
+
for (let name in node.attrs)
|
|
1761
|
+
attrs[name] = node.attrs[name];
|
|
1762
|
+
attrs[this.attr] = this.value;
|
|
1763
|
+
let updated = node.type.create(attrs, null, node.marks);
|
|
1764
|
+
return StepResult.fromReplace(doc, this.pos, this.pos + 1, new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(updated), 0, node.isLeaf ? 0 : 1));
|
|
1765
|
+
}
|
|
1766
|
+
getMap() {
|
|
1767
|
+
return StepMap.empty;
|
|
1768
|
+
}
|
|
1769
|
+
invert(doc) {
|
|
1770
|
+
return new AttrStep(this.pos, this.attr, doc.nodeAt(this.pos).attrs[this.attr]);
|
|
1771
|
+
}
|
|
1772
|
+
map(mapping) {
|
|
1773
|
+
let pos = mapping.mapResult(this.pos, 1);
|
|
1774
|
+
return pos.deletedAfter ? null : new AttrStep(pos.pos, this.attr, this.value);
|
|
1775
|
+
}
|
|
1776
|
+
toJSON() {
|
|
1777
|
+
return { stepType: "attr", pos: this.pos, attr: this.attr, value: this.value };
|
|
1778
|
+
}
|
|
1779
|
+
static fromJSON(schema, json) {
|
|
1780
|
+
if (typeof json.pos != "number" || typeof json.attr != "string")
|
|
1781
|
+
throw new RangeError("Invalid input for AttrStep.fromJSON");
|
|
1782
|
+
return new AttrStep(json.pos, json.attr, json.value);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
Step.jsonID("attr", AttrStep);
|
|
1786
|
+
|
|
1787
|
+
/**
|
|
1788
|
+
@internal
|
|
1789
|
+
*/
|
|
1790
|
+
let TransformError = class extends Error {
|
|
1791
|
+
};
|
|
1792
|
+
TransformError = function TransformError(message) {
|
|
1793
|
+
let err = Error.call(this, message);
|
|
1794
|
+
err.__proto__ = TransformError.prototype;
|
|
1795
|
+
return err;
|
|
1796
|
+
};
|
|
1797
|
+
TransformError.prototype = Object.create(Error.prototype);
|
|
1798
|
+
TransformError.prototype.constructor = TransformError;
|
|
1799
|
+
TransformError.prototype.name = "TransformError";
|
|
1800
|
+
/**
|
|
1801
|
+
Abstraction to build up and track an array of
|
|
1802
|
+
[steps](https://prosemirror.net/docs/ref/#transform.Step) representing a document transformation.
|
|
1803
|
+
|
|
1804
|
+
Most transforming methods return the `Transform` object itself, so
|
|
1805
|
+
that they can be chained.
|
|
1806
|
+
*/
|
|
1807
|
+
class Transform {
|
|
1808
|
+
/**
|
|
1809
|
+
Create a transform that starts with the given document.
|
|
1810
|
+
*/
|
|
1811
|
+
constructor(
|
|
1812
|
+
/**
|
|
1813
|
+
The current document (the result of applying the steps in the
|
|
1814
|
+
transform).
|
|
1815
|
+
*/
|
|
1816
|
+
doc) {
|
|
1817
|
+
this.doc = doc;
|
|
1818
|
+
/**
|
|
1819
|
+
The steps in this transform.
|
|
1820
|
+
*/
|
|
1821
|
+
this.steps = [];
|
|
1822
|
+
/**
|
|
1823
|
+
The documents before each of the steps.
|
|
1824
|
+
*/
|
|
1825
|
+
this.docs = [];
|
|
1826
|
+
/**
|
|
1827
|
+
A mapping with the maps for each of the steps in this transform.
|
|
1828
|
+
*/
|
|
1829
|
+
this.mapping = new Mapping;
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
The starting document.
|
|
1833
|
+
*/
|
|
1834
|
+
get before() { return this.docs.length ? this.docs[0] : this.doc; }
|
|
1835
|
+
/**
|
|
1836
|
+
Apply a new step in this transform, saving the result. Throws an
|
|
1837
|
+
error when the step fails.
|
|
1838
|
+
*/
|
|
1839
|
+
step(step) {
|
|
1840
|
+
let result = this.maybeStep(step);
|
|
1841
|
+
if (result.failed)
|
|
1842
|
+
throw new TransformError(result.failed);
|
|
1843
|
+
return this;
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
Try to apply a step in this transformation, ignoring it if it
|
|
1847
|
+
fails. Returns the step result.
|
|
1848
|
+
*/
|
|
1849
|
+
maybeStep(step) {
|
|
1850
|
+
let result = step.apply(this.doc);
|
|
1851
|
+
if (!result.failed)
|
|
1852
|
+
this.addStep(step, result.doc);
|
|
1853
|
+
return result;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
True when the document has been changed (when there are any
|
|
1857
|
+
steps).
|
|
1858
|
+
*/
|
|
1859
|
+
get docChanged() {
|
|
1860
|
+
return this.steps.length > 0;
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
@internal
|
|
1864
|
+
*/
|
|
1865
|
+
addStep(step, doc) {
|
|
1866
|
+
this.docs.push(this.doc);
|
|
1867
|
+
this.steps.push(step);
|
|
1868
|
+
this.mapping.appendMap(step.getMap());
|
|
1869
|
+
this.doc = doc;
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
Replace the part of the document between `from` and `to` with the
|
|
1873
|
+
given `slice`.
|
|
1874
|
+
*/
|
|
1875
|
+
replace(from, to = from, slice = prosemirrorModel.Slice.empty) {
|
|
1876
|
+
let step = replaceStep(this.doc, from, to, slice);
|
|
1877
|
+
if (step)
|
|
1878
|
+
this.step(step);
|
|
1879
|
+
return this;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
Replace the given range with the given content, which may be a
|
|
1883
|
+
fragment, node, or array of nodes.
|
|
1884
|
+
*/
|
|
1885
|
+
replaceWith(from, to, content) {
|
|
1886
|
+
return this.replace(from, to, new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(content), 0, 0));
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
Delete the content between the given positions.
|
|
1890
|
+
*/
|
|
1891
|
+
delete(from, to) {
|
|
1892
|
+
return this.replace(from, to, prosemirrorModel.Slice.empty);
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
Insert the given content at the given position.
|
|
1896
|
+
*/
|
|
1897
|
+
insert(pos, content) {
|
|
1898
|
+
return this.replaceWith(pos, pos, content);
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
Replace a range of the document with a given slice, using
|
|
1902
|
+
`from`, `to`, and the slice's
|
|
1903
|
+
[`openStart`](https://prosemirror.net/docs/ref/#model.Slice.openStart) property as hints, rather
|
|
1904
|
+
than fixed start and end points. This method may grow the
|
|
1905
|
+
replaced area or close open nodes in the slice in order to get a
|
|
1906
|
+
fit that is more in line with WYSIWYG expectations, by dropping
|
|
1907
|
+
fully covered parent nodes of the replaced region when they are
|
|
1908
|
+
marked [non-defining as
|
|
1909
|
+
context](https://prosemirror.net/docs/ref/#model.NodeSpec.definingAsContext), or including an
|
|
1910
|
+
open parent node from the slice that _is_ marked as [defining
|
|
1911
|
+
its content](https://prosemirror.net/docs/ref/#model.NodeSpec.definingForContent).
|
|
1912
|
+
|
|
1913
|
+
This is the method, for example, to handle paste. The similar
|
|
1914
|
+
[`replace`](https://prosemirror.net/docs/ref/#transform.Transform.replace) method is a more
|
|
1915
|
+
primitive tool which will _not_ move the start and end of its given
|
|
1916
|
+
range, and is useful in situations where you need more precise
|
|
1917
|
+
control over what happens.
|
|
1918
|
+
*/
|
|
1919
|
+
replaceRange(from, to, slice) {
|
|
1920
|
+
replaceRange(this, from, to, slice);
|
|
1921
|
+
return this;
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
Replace the given range with a node, but use `from` and `to` as
|
|
1925
|
+
hints, rather than precise positions. When from and to are the same
|
|
1926
|
+
and are at the start or end of a parent node in which the given
|
|
1927
|
+
node doesn't fit, this method may _move_ them out towards a parent
|
|
1928
|
+
that does allow the given node to be placed. When the given range
|
|
1929
|
+
completely covers a parent node, this method may completely replace
|
|
1930
|
+
that parent node.
|
|
1931
|
+
*/
|
|
1932
|
+
replaceRangeWith(from, to, node) {
|
|
1933
|
+
replaceRangeWith(this, from, to, node);
|
|
1934
|
+
return this;
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
Delete the given range, expanding it to cover fully covered
|
|
1938
|
+
parent nodes until a valid replace is found.
|
|
1939
|
+
*/
|
|
1940
|
+
deleteRange(from, to) {
|
|
1941
|
+
deleteRange(this, from, to);
|
|
1942
|
+
return this;
|
|
1943
|
+
}
|
|
1944
|
+
/**
|
|
1945
|
+
Split the content in the given range off from its parent, if there
|
|
1946
|
+
is sibling content before or after it, and move it up the tree to
|
|
1947
|
+
the depth specified by `target`. You'll probably want to use
|
|
1948
|
+
[`liftTarget`](https://prosemirror.net/docs/ref/#transform.liftTarget) to compute `target`, to make
|
|
1949
|
+
sure the lift is valid.
|
|
1950
|
+
*/
|
|
1951
|
+
lift(range, target) {
|
|
1952
|
+
lift(this, range, target);
|
|
1953
|
+
return this;
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
Join the blocks around the given position. If depth is 2, their
|
|
1957
|
+
last and first siblings are also joined, and so on.
|
|
1958
|
+
*/
|
|
1959
|
+
join(pos, depth = 1) {
|
|
1960
|
+
join(this, pos, depth);
|
|
1961
|
+
return this;
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
Wrap the given [range](https://prosemirror.net/docs/ref/#model.NodeRange) in the given set of wrappers.
|
|
1965
|
+
The wrappers are assumed to be valid in this position, and should
|
|
1966
|
+
probably be computed with [`findWrapping`](https://prosemirror.net/docs/ref/#transform.findWrapping).
|
|
1967
|
+
*/
|
|
1968
|
+
wrap(range, wrappers) {
|
|
1969
|
+
wrap(this, range, wrappers);
|
|
1970
|
+
return this;
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
Set the type of all textblocks (partly) between `from` and `to` to
|
|
1974
|
+
the given node type with the given attributes.
|
|
1975
|
+
*/
|
|
1976
|
+
setBlockType(from, to = from, type, attrs = null) {
|
|
1977
|
+
setBlockType(this, from, to, type, attrs);
|
|
1978
|
+
return this;
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
Change the type, attributes, and/or marks of the node at `pos`.
|
|
1982
|
+
When `type` isn't given, the existing node type is preserved,
|
|
1983
|
+
*/
|
|
1984
|
+
setNodeMarkup(pos, type, attrs = null, marks = []) {
|
|
1985
|
+
setNodeMarkup(this, pos, type, attrs, marks);
|
|
1986
|
+
return this;
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
Set a single attribute on a given node to a new value.
|
|
1990
|
+
*/
|
|
1991
|
+
setNodeAttribute(pos, attr, value) {
|
|
1992
|
+
this.step(new AttrStep(pos, attr, value));
|
|
1993
|
+
return this;
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
Add a mark to the node at position `pos`.
|
|
1997
|
+
*/
|
|
1998
|
+
addNodeMark(pos, mark) {
|
|
1999
|
+
this.step(new AddNodeMarkStep(pos, mark));
|
|
2000
|
+
return this;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
Remove a mark (or a mark of the given type) from the node at
|
|
2004
|
+
position `pos`.
|
|
2005
|
+
*/
|
|
2006
|
+
removeNodeMark(pos, mark) {
|
|
2007
|
+
if (!(mark instanceof prosemirrorModel.Mark)) {
|
|
2008
|
+
let node = this.doc.nodeAt(pos);
|
|
2009
|
+
if (!node)
|
|
2010
|
+
throw new RangeError("No node at position " + pos);
|
|
2011
|
+
mark = mark.isInSet(node.marks);
|
|
2012
|
+
if (!mark)
|
|
2013
|
+
return this;
|
|
2014
|
+
}
|
|
2015
|
+
this.step(new RemoveNodeMarkStep(pos, mark));
|
|
2016
|
+
return this;
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
Split the node at the given position, and optionally, if `depth` is
|
|
2020
|
+
greater than one, any number of nodes above that. By default, the
|
|
2021
|
+
parts split off will inherit the node type of the original node.
|
|
2022
|
+
This can be changed by passing an array of types and attributes to
|
|
2023
|
+
use after the split.
|
|
2024
|
+
*/
|
|
2025
|
+
split(pos, depth = 1, typesAfter) {
|
|
2026
|
+
split(this, pos, depth, typesAfter);
|
|
2027
|
+
return this;
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
Add the given mark to the inline content between `from` and `to`.
|
|
2031
|
+
*/
|
|
2032
|
+
addMark(from, to, mark) {
|
|
2033
|
+
addMark(this, from, to, mark);
|
|
2034
|
+
return this;
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
Remove marks from inline nodes between `from` and `to`. When
|
|
2038
|
+
`mark` is a single mark, remove precisely that mark. When it is
|
|
2039
|
+
a mark type, remove all marks of that type. When it is null,
|
|
2040
|
+
remove all marks of any type.
|
|
2041
|
+
*/
|
|
2042
|
+
removeMark(from, to, mark) {
|
|
2043
|
+
removeMark(this, from, to, mark);
|
|
2044
|
+
return this;
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
Removes all marks and nodes from the content of the node at
|
|
2048
|
+
`pos` that don't match the given new parent node type. Accepts
|
|
2049
|
+
an optional starting [content match](https://prosemirror.net/docs/ref/#model.ContentMatch) as
|
|
2050
|
+
third argument.
|
|
2051
|
+
*/
|
|
2052
|
+
clearIncompatible(pos, parentType, match) {
|
|
2053
|
+
clearIncompatible(this, pos, parentType, match);
|
|
2054
|
+
return this;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// Because working with row and column-spanning cells is not quite
|
|
2059
|
+
// trivial, this code builds up a descriptive structure for a given
|
|
2060
|
+
// table node. The structures are cached with the (persistent) table
|
|
2061
|
+
// nodes as key, so that they only have to be recomputed when the
|
|
2062
|
+
// content of the table changes.
|
|
2063
|
+
//
|
|
2064
|
+
// This does mean that they have to store table-relative, not
|
|
2065
|
+
// document-relative positions. So code that uses them will typically
|
|
2066
|
+
// compute the start position of the table and offset positions passed
|
|
2067
|
+
// to or gotten from this structure by that amount.
|
|
2068
|
+
let readFromCache, addToCache; // Prefer using a weak map to cache table maps. Fall back on a
|
|
2069
|
+
// fixed-size cache if that's not supported.
|
|
2070
|
+
|
|
2071
|
+
if (typeof WeakMap != 'undefined') {
|
|
2072
|
+
// eslint-disable-next-line
|
|
2073
|
+
let cache = new WeakMap();
|
|
2074
|
+
|
|
2075
|
+
readFromCache = key => cache.get(key);
|
|
2076
|
+
|
|
2077
|
+
addToCache = (key, value) => {
|
|
2078
|
+
cache.set(key, value);
|
|
2079
|
+
return value;
|
|
2080
|
+
};
|
|
2081
|
+
} else {
|
|
2082
|
+
let cache = [],
|
|
2083
|
+
cacheSize = 10,
|
|
2084
|
+
cachePos = 0;
|
|
2085
|
+
|
|
2086
|
+
readFromCache = key => {
|
|
2087
|
+
for (let i = 0; i < cache.length; i += 2) if (cache[i] == key) return cache[i + 1];
|
|
2088
|
+
};
|
|
2089
|
+
|
|
2090
|
+
addToCache = (key, value) => {
|
|
2091
|
+
if (cachePos == cacheSize) cachePos = 0;
|
|
2092
|
+
cache[cachePos++] = key;
|
|
2093
|
+
return cache[cachePos++] = value;
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
class Rect {
|
|
2098
|
+
constructor(left, top, right, bottom) {
|
|
2099
|
+
this.left = left;
|
|
2100
|
+
this.top = top;
|
|
2101
|
+
this.right = right;
|
|
2102
|
+
this.bottom = bottom;
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
} // ::- A table map describes the structore of a given table. To avoid
|
|
2106
|
+
// recomputing them all the time, they are cached per table node. To
|
|
2107
|
+
// be able to do that, positions saved in the map are relative to the
|
|
2108
|
+
// start of the table, rather than the start of the document.
|
|
2109
|
+
|
|
2110
|
+
class TableMap {
|
|
2111
|
+
constructor(width, height, map, problems) {
|
|
2112
|
+
// :: number The width of the table
|
|
2113
|
+
this.width = width; // :: number The table's height
|
|
2114
|
+
|
|
2115
|
+
this.height = height; // :: [number] A width * height array with the start position of
|
|
2116
|
+
// the cell covering that part of the table in each slot
|
|
2117
|
+
|
|
2118
|
+
this.map = map; // An optional array of problems (cell overlap or non-rectangular
|
|
2119
|
+
// shape) for the table, used by the table normalizer.
|
|
2120
|
+
|
|
2121
|
+
this.problems = problems;
|
|
2122
|
+
} // :: (number) → Rect
|
|
2123
|
+
// Find the dimensions of the cell at the given position.
|
|
2124
|
+
|
|
2125
|
+
|
|
2126
|
+
findCell(pos) {
|
|
2127
|
+
for (let i = 0; i < this.map.length; i++) {
|
|
2128
|
+
let curPos = this.map[i];
|
|
2129
|
+
if (curPos != pos) continue;
|
|
2130
|
+
let left = i % this.width,
|
|
2131
|
+
top = i / this.width | 0;
|
|
2132
|
+
let right = left + 1,
|
|
2133
|
+
bottom = top + 1;
|
|
2134
|
+
|
|
2135
|
+
for (let j = 1; right < this.width && this.map[i + j] == curPos; j++) right++;
|
|
2136
|
+
|
|
2137
|
+
for (let j = 1; bottom < this.height && this.map[i + this.width * j] == curPos; j++) bottom++;
|
|
2138
|
+
|
|
2139
|
+
return new Rect(left, top, right, bottom);
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
throw new RangeError('No cell with offset ' + pos + ' found');
|
|
2143
|
+
} // :: (number) → number
|
|
2144
|
+
// Find the left side of the cell at the given position.
|
|
2145
|
+
|
|
2146
|
+
|
|
2147
|
+
colCount(pos) {
|
|
2148
|
+
for (let i = 0; i < this.map.length; i++) if (this.map[i] == pos) return i % this.width;
|
|
2149
|
+
|
|
2150
|
+
throw new RangeError('No cell with offset ' + pos + ' found');
|
|
2151
|
+
} // :: (number, string, number) → ?number
|
|
2152
|
+
// Find the next cell in the given direction, starting from the cell
|
|
2153
|
+
// at `pos`, if any.
|
|
2154
|
+
|
|
2155
|
+
|
|
2156
|
+
nextCell(pos, axis, dir) {
|
|
2157
|
+
let {
|
|
2158
|
+
left,
|
|
2159
|
+
right,
|
|
2160
|
+
top,
|
|
2161
|
+
bottom
|
|
2162
|
+
} = this.findCell(pos);
|
|
2163
|
+
|
|
2164
|
+
if (axis == 'horiz') {
|
|
2165
|
+
if (dir < 0 ? left == 0 : right == this.width) return null;
|
|
2166
|
+
return this.map[top * this.width + (dir < 0 ? left - 1 : right)];
|
|
2167
|
+
} else {
|
|
2168
|
+
if (dir < 0 ? top == 0 : bottom == this.height) return null;
|
|
2169
|
+
return this.map[left + this.width * (dir < 0 ? top - 1 : bottom)];
|
|
2170
|
+
}
|
|
2171
|
+
} // :: (number, number) → Rect
|
|
2172
|
+
// Get the rectangle spanning the two given cells.
|
|
2173
|
+
|
|
2174
|
+
|
|
2175
|
+
rectBetween(a, b) {
|
|
2176
|
+
let {
|
|
2177
|
+
left: leftA,
|
|
2178
|
+
right: rightA,
|
|
2179
|
+
top: topA,
|
|
2180
|
+
bottom: bottomA
|
|
2181
|
+
} = this.findCell(a);
|
|
2182
|
+
let {
|
|
2183
|
+
left: leftB,
|
|
2184
|
+
right: rightB,
|
|
2185
|
+
top: topB,
|
|
2186
|
+
bottom: bottomB
|
|
2187
|
+
} = this.findCell(b);
|
|
2188
|
+
return new Rect(Math.min(leftA, leftB), Math.min(topA, topB), Math.max(rightA, rightB), Math.max(bottomA, bottomB));
|
|
2189
|
+
} // :: (Rect) → [number]
|
|
2190
|
+
// Return the position of all cells that have the top left corner in
|
|
2191
|
+
// the given rectangle.
|
|
2192
|
+
|
|
2193
|
+
|
|
2194
|
+
cellsInRect(rect) {
|
|
2195
|
+
let result = [],
|
|
2196
|
+
seen = {};
|
|
2197
|
+
|
|
2198
|
+
for (let row = rect.top; row < rect.bottom; row++) {
|
|
2199
|
+
for (let col = rect.left; col < rect.right; col++) {
|
|
2200
|
+
let index = row * this.width + col,
|
|
2201
|
+
pos = this.map[index];
|
|
2202
|
+
if (seen[pos]) continue;
|
|
2203
|
+
seen[pos] = true;
|
|
2204
|
+
if ((col != rect.left || !col || this.map[index - 1] != pos) && (row != rect.top || !row || this.map[index - this.width] != pos)) result.push(pos);
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
return result;
|
|
2209
|
+
} // :: (number, number, Node) → number
|
|
2210
|
+
// Return the position at which the cell at the given row and column
|
|
2211
|
+
// starts, or would start, if a cell started there.
|
|
2212
|
+
|
|
2213
|
+
|
|
2214
|
+
positionAt(row, col, table) {
|
|
2215
|
+
for (let i = 0, rowStart = 0;; i++) {
|
|
2216
|
+
let rowEnd = rowStart + table.child(i).nodeSize;
|
|
2217
|
+
|
|
2218
|
+
if (i == row) {
|
|
2219
|
+
let index = col + row * this.width,
|
|
2220
|
+
rowEndIndex = (row + 1) * this.width; // Skip past cells from previous rows (via rowspan)
|
|
2221
|
+
|
|
2222
|
+
while (index < rowEndIndex && this.map[index] < rowStart) index++;
|
|
2223
|
+
|
|
2224
|
+
return index == rowEndIndex ? rowEnd - 1 : this.map[index];
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
rowStart = rowEnd;
|
|
2228
|
+
}
|
|
2229
|
+
} // :: (Node) → TableMap
|
|
2230
|
+
// Find the table map for the given table node.
|
|
2231
|
+
|
|
2232
|
+
|
|
2233
|
+
static get(table) {
|
|
2234
|
+
return readFromCache(table) || addToCache(table, computeMap(table));
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
} // Compute a table map.
|
|
2238
|
+
|
|
2239
|
+
function computeMap(table) {
|
|
2240
|
+
if (table.type.spec.tableRole != 'table') throw new RangeError('Not a table node: ' + table.type.name);
|
|
2241
|
+
let width = findWidth(table),
|
|
2242
|
+
height = table.childCount;
|
|
2243
|
+
let map = [],
|
|
2244
|
+
mapPos = 0,
|
|
2245
|
+
problems = null,
|
|
2246
|
+
colWidths = [];
|
|
2247
|
+
|
|
2248
|
+
for (let i = 0, e = width * height; i < e; i++) map[i] = 0;
|
|
2249
|
+
|
|
2250
|
+
for (let row = 0, pos = 0; row < height; row++) {
|
|
2251
|
+
let rowNode = table.child(row);
|
|
2252
|
+
pos++;
|
|
2253
|
+
|
|
2254
|
+
for (let i = 0;; i++) {
|
|
2255
|
+
while (mapPos < map.length && map[mapPos] != 0) mapPos++;
|
|
2256
|
+
|
|
2257
|
+
if (i == rowNode.childCount) break;
|
|
2258
|
+
let cellNode = rowNode.child(i),
|
|
2259
|
+
{
|
|
2260
|
+
colspan,
|
|
2261
|
+
rowspan,
|
|
2262
|
+
colwidth
|
|
2263
|
+
} = cellNode.attrs;
|
|
2264
|
+
|
|
2265
|
+
for (let h = 0; h < rowspan; h++) {
|
|
2266
|
+
if (h + row >= height) {
|
|
2267
|
+
(problems || (problems = [])).push({
|
|
2268
|
+
type: 'overlong_rowspan',
|
|
2269
|
+
pos,
|
|
2270
|
+
n: rowspan - h
|
|
2271
|
+
});
|
|
2272
|
+
break;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
let start = mapPos + h * width;
|
|
2276
|
+
|
|
2277
|
+
for (let w = 0; w < colspan; w++) {
|
|
2278
|
+
if (map[start + w] == 0) map[start + w] = pos;else (problems || (problems = [])).push({
|
|
2279
|
+
type: 'collision',
|
|
2280
|
+
row,
|
|
2281
|
+
pos,
|
|
2282
|
+
n: colspan - w
|
|
2283
|
+
});
|
|
2284
|
+
let colW = colwidth && colwidth[w];
|
|
2285
|
+
|
|
2286
|
+
if (colW) {
|
|
2287
|
+
let widthIndex = (start + w) % width * 2,
|
|
2288
|
+
prev = colWidths[widthIndex];
|
|
2289
|
+
|
|
2290
|
+
if (prev == null || prev != colW && colWidths[widthIndex + 1] == 1) {
|
|
2291
|
+
colWidths[widthIndex] = colW;
|
|
2292
|
+
colWidths[widthIndex + 1] = 1;
|
|
2293
|
+
} else if (prev == colW) {
|
|
2294
|
+
colWidths[widthIndex + 1]++;
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
mapPos += colspan;
|
|
2301
|
+
pos += cellNode.nodeSize;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
let expectedPos = (row + 1) * width,
|
|
2305
|
+
missing = 0;
|
|
2306
|
+
|
|
2307
|
+
while (mapPos < expectedPos) if (map[mapPos++] == 0) missing++;
|
|
2308
|
+
|
|
2309
|
+
if (missing) (problems || (problems = [])).push({
|
|
2310
|
+
type: 'missing',
|
|
2311
|
+
row,
|
|
2312
|
+
n: missing
|
|
2313
|
+
});
|
|
2314
|
+
pos++;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
let tableMap = new TableMap(width, height, map, problems),
|
|
2318
|
+
badWidths = false; // For columns that have defined widths, but whose widths disagree
|
|
2319
|
+
// between rows, fix up the cells whose width doesn't match the
|
|
2320
|
+
// computed one.
|
|
2321
|
+
|
|
2322
|
+
for (let i = 0; !badWidths && i < colWidths.length; i += 2) if (colWidths[i] != null && colWidths[i + 1] < height) badWidths = true;
|
|
2323
|
+
|
|
2324
|
+
if (badWidths) findBadColWidths(tableMap, colWidths, table);
|
|
2325
|
+
return tableMap;
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
function findWidth(table) {
|
|
2329
|
+
let width = -1,
|
|
2330
|
+
hasRowSpan = false;
|
|
2331
|
+
|
|
2332
|
+
for (let row = 0; row < table.childCount; row++) {
|
|
2333
|
+
let rowNode = table.child(row),
|
|
2334
|
+
rowWidth = 0;
|
|
2335
|
+
if (hasRowSpan) for (let j = 0; j < row; j++) {
|
|
2336
|
+
let prevRow = table.child(j);
|
|
2337
|
+
|
|
2338
|
+
for (let i = 0; i < prevRow.childCount; i++) {
|
|
2339
|
+
let cell = prevRow.child(i);
|
|
2340
|
+
if (j + cell.attrs.rowspan > row) rowWidth += cell.attrs.colspan;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
for (let i = 0; i < rowNode.childCount; i++) {
|
|
2345
|
+
let cell = rowNode.child(i);
|
|
2346
|
+
rowWidth += cell.attrs.colspan;
|
|
2347
|
+
if (cell.attrs.rowspan > 1) hasRowSpan = true;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
if (width == -1) width = rowWidth;else if (width != rowWidth) width = Math.max(width, rowWidth);
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
return width;
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
function findBadColWidths(map, colWidths, table) {
|
|
2357
|
+
if (!map.problems) map.problems = [];
|
|
2358
|
+
|
|
2359
|
+
for (let i = 0, seen = {}; i < map.map.length; i++) {
|
|
2360
|
+
let pos = map.map[i];
|
|
2361
|
+
if (seen[pos]) continue;
|
|
2362
|
+
seen[pos] = true;
|
|
2363
|
+
let node = table.nodeAt(pos),
|
|
2364
|
+
updated = null;
|
|
2365
|
+
|
|
2366
|
+
for (let j = 0; j < node.attrs.colspan; j++) {
|
|
2367
|
+
let col = (i + j) % map.width,
|
|
2368
|
+
colWidth = colWidths[col * 2];
|
|
2369
|
+
if (colWidth != null && (!node.attrs.colwidth || node.attrs.colwidth[j] != colWidth)) (updated || (updated = freshColWidth(node.attrs)))[j] = colWidth;
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
if (updated) map.problems.unshift({
|
|
2373
|
+
type: 'colwidth mismatch',
|
|
2374
|
+
pos,
|
|
2375
|
+
colwidth: updated
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
function freshColWidth(attrs) {
|
|
2381
|
+
if (attrs.colwidth) return attrs.colwidth.slice();
|
|
2382
|
+
let result = [];
|
|
2383
|
+
|
|
2384
|
+
for (let i = 0; i < attrs.colspan; i++) result.push(0);
|
|
2385
|
+
|
|
2386
|
+
return result;
|
|
2387
|
+
}
|
|
2388
|
+
function tableNodeTypes(schema) {
|
|
2389
|
+
let result = schema.cached.tableNodeTypes;
|
|
2390
|
+
|
|
2391
|
+
if (!result) {
|
|
2392
|
+
result = schema.cached.tableNodeTypes = {};
|
|
2393
|
+
|
|
2394
|
+
for (let name in schema.nodes) {
|
|
2395
|
+
let type = schema.nodes[name],
|
|
2396
|
+
role = type.spec.tableRole;
|
|
2397
|
+
if (role) result[role] = type;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
return result;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
// Various helper function for working with tables
|
|
2405
|
+
const key$1 = new prosemirrorState.PluginKey('selectingCells');
|
|
2406
|
+
function cellAround($pos) {
|
|
2407
|
+
for (let d = $pos.depth - 1; d > 0; d--) if ($pos.node(d).type.spec.tableRole == 'row') return $pos.node(0).resolve($pos.before(d + 1));
|
|
2408
|
+
|
|
2409
|
+
return null;
|
|
2410
|
+
}
|
|
2411
|
+
function cellWrapping($pos) {
|
|
2412
|
+
for (let d = $pos.depth; d > 0; d--) {
|
|
2413
|
+
// Sometimes the cell can be in the same depth.
|
|
2414
|
+
const role = $pos.node(d).type.spec.tableRole;
|
|
2415
|
+
if (role === 'cell' || role === 'header_cell') return $pos.node(d);
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
return null;
|
|
2419
|
+
}
|
|
2420
|
+
function isInTable(state) {
|
|
2421
|
+
let $head = state.selection.$head;
|
|
2422
|
+
|
|
2423
|
+
for (let d = $head.depth; d > 0; d--) if ($head.node(d).type.spec.tableRole == 'row') return true;
|
|
2424
|
+
|
|
2425
|
+
return false;
|
|
2426
|
+
}
|
|
2427
|
+
function selectionCell(state) {
|
|
2428
|
+
let sel = state.selection;
|
|
2429
|
+
|
|
2430
|
+
if (sel.$anchorCell) {
|
|
2431
|
+
return sel.$anchorCell.pos > sel.$headCell.pos ? sel.$anchorCell : sel.$headCell;
|
|
2432
|
+
} else if (sel.node && sel.node.type.spec.tableRole == 'cell') {
|
|
2433
|
+
return sel.$anchor;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
return cellAround(sel.$head) || cellNear(sel.$head);
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
function cellNear($pos) {
|
|
2440
|
+
for (let after = $pos.nodeAfter, pos = $pos.pos; after; after = after.firstChild, pos++) {
|
|
2441
|
+
let role = after.type.spec.tableRole;
|
|
2442
|
+
if (role == 'cell' || role == 'header_cell') return $pos.doc.resolve(pos);
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
for (let before = $pos.nodeBefore, pos = $pos.pos; before; before = before.lastChild, pos--) {
|
|
2446
|
+
let role = before.type.spec.tableRole;
|
|
2447
|
+
if (role == 'cell' || role == 'header_cell') return $pos.doc.resolve(pos - before.nodeSize);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
function pointsAtCell($pos) {
|
|
2452
|
+
return $pos.parent.type.spec.tableRole == 'row' && $pos.nodeAfter;
|
|
2453
|
+
}
|
|
2454
|
+
function moveCellForward($pos) {
|
|
2455
|
+
return $pos.node(0).resolve($pos.pos + $pos.nodeAfter.nodeSize);
|
|
2456
|
+
}
|
|
2457
|
+
function inSameTable($a, $b) {
|
|
2458
|
+
return $a.depth == $b.depth && $a.pos >= $b.start(-1) && $a.pos <= $b.end(-1);
|
|
2459
|
+
}
|
|
2460
|
+
function nextCell($pos, axis, dir) {
|
|
2461
|
+
let start = $pos.start(-1),
|
|
2462
|
+
map = TableMap.get($pos.node(-1));
|
|
2463
|
+
let moved = map.nextCell($pos.pos - start, axis, dir);
|
|
2464
|
+
return moved == null ? null : $pos.node(0).resolve(start + moved);
|
|
2465
|
+
}
|
|
2466
|
+
function setAttr(attrs, name, value) {
|
|
2467
|
+
let result = {};
|
|
2468
|
+
|
|
2469
|
+
for (let prop in attrs) result[prop] = attrs[prop];
|
|
2470
|
+
|
|
2471
|
+
result[name] = value;
|
|
2472
|
+
return result;
|
|
2473
|
+
}
|
|
2474
|
+
function removeColSpan(attrs, pos, n = 1) {
|
|
2475
|
+
let result = setAttr(attrs, 'colspan', attrs.colspan - n);
|
|
2476
|
+
|
|
2477
|
+
if (result.colwidth) {
|
|
2478
|
+
result.colwidth = result.colwidth.slice();
|
|
2479
|
+
result.colwidth.splice(pos, n);
|
|
2480
|
+
if (!result.colwidth.some(w => w > 0)) result.colwidth = null;
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
return result;
|
|
2484
|
+
}
|
|
2485
|
+
function addColSpan(attrs, pos, n = 1) {
|
|
2486
|
+
let result = setAttr(attrs, 'colspan', attrs.colspan + n);
|
|
2487
|
+
|
|
2488
|
+
if (result.colwidth) {
|
|
2489
|
+
result.colwidth = result.colwidth.slice();
|
|
2490
|
+
|
|
2491
|
+
for (let i = 0; i < n; i++) result.colwidth.splice(pos, 0, 0);
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
return result;
|
|
2495
|
+
}
|
|
2496
|
+
function columnIsHeader(map, table, col) {
|
|
2497
|
+
let headerCell = tableNodeTypes(table.type.schema).header_cell;
|
|
2498
|
+
|
|
2499
|
+
for (let row = 0; row < map.height; row++) if (table.nodeAt(map.map[col + row * map.width]).type != headerCell) return false;
|
|
2500
|
+
|
|
2501
|
+
return true;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
// This file defines a ProseMirror selection subclass that models
|
|
2505
|
+
// subclass that represents a cell selection spanning part of a table.
|
|
2506
|
+
// With the plugin enabled, these will be created when the user
|
|
2507
|
+
// selects across cells, and will be drawn by giving selected cells a
|
|
2508
|
+
// `selectedCell` CSS class.
|
|
2509
|
+
|
|
2510
|
+
class CellSelection extends prosemirrorState.Selection {
|
|
2511
|
+
// :: (ResolvedPos, ?ResolvedPos)
|
|
2512
|
+
// A table selection is identified by its anchor and head cells. The
|
|
2513
|
+
// positions given to this constructor should point _before_ two
|
|
2514
|
+
// cells in the same table. They may be the same, to select a single
|
|
2515
|
+
// cell.
|
|
2516
|
+
constructor($anchorCell, $headCell = $anchorCell) {
|
|
2517
|
+
let table = $anchorCell.node(-1),
|
|
2518
|
+
map = TableMap.get(table),
|
|
2519
|
+
start = $anchorCell.start(-1);
|
|
2520
|
+
let rect = map.rectBetween($anchorCell.pos - start, $headCell.pos - start);
|
|
2521
|
+
let doc = $anchorCell.node(0);
|
|
2522
|
+
let cells = map.cellsInRect(rect).filter(p => p != $headCell.pos - start); // Make the head cell the first range, so that it counts as the
|
|
2523
|
+
// primary part of the selection
|
|
2524
|
+
|
|
2525
|
+
cells.unshift($headCell.pos - start);
|
|
2526
|
+
let ranges = cells.map(pos => {
|
|
2527
|
+
let cell = table.nodeAt(pos),
|
|
2528
|
+
from = pos + start + 1;
|
|
2529
|
+
return new prosemirrorState.SelectionRange(doc.resolve(from), doc.resolve(from + cell.content.size));
|
|
2530
|
+
});
|
|
2531
|
+
super(ranges[0].$from, ranges[0].$to, ranges); // :: ResolvedPos
|
|
2532
|
+
// A resolved position pointing _in front of_ the anchor cell (the one
|
|
2533
|
+
// that doesn't move when extending the selection).
|
|
2534
|
+
|
|
2535
|
+
this.$anchorCell = $anchorCell; // :: ResolvedPos
|
|
2536
|
+
// A resolved position pointing in front of the head cell (the one
|
|
2537
|
+
// moves when extending the selection).
|
|
2538
|
+
|
|
2539
|
+
this.$headCell = $headCell;
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
map(doc, mapping) {
|
|
2543
|
+
let $anchorCell = doc.resolve(mapping.map(this.$anchorCell.pos));
|
|
2544
|
+
let $headCell = doc.resolve(mapping.map(this.$headCell.pos));
|
|
2545
|
+
|
|
2546
|
+
if (pointsAtCell($anchorCell) && pointsAtCell($headCell) && inSameTable($anchorCell, $headCell)) {
|
|
2547
|
+
let tableChanged = this.$anchorCell.node(-1) != $anchorCell.node(-1);
|
|
2548
|
+
if (tableChanged && this.isRowSelection()) return CellSelection.rowSelection($anchorCell, $headCell);else if (tableChanged && this.isColSelection()) return CellSelection.colSelection($anchorCell, $headCell);else return new CellSelection($anchorCell, $headCell);
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
return prosemirrorState.TextSelection.between($anchorCell, $headCell);
|
|
2552
|
+
} // :: () → Slice
|
|
2553
|
+
// Returns a rectangular slice of table rows containing the selected
|
|
2554
|
+
// cells.
|
|
2555
|
+
|
|
2556
|
+
|
|
2557
|
+
content() {
|
|
2558
|
+
let table = this.$anchorCell.node(-1),
|
|
2559
|
+
map = TableMap.get(table),
|
|
2560
|
+
start = this.$anchorCell.start(-1);
|
|
2561
|
+
let rect = map.rectBetween(this.$anchorCell.pos - start, this.$headCell.pos - start);
|
|
2562
|
+
let seen = {},
|
|
2563
|
+
rows = [];
|
|
2564
|
+
|
|
2565
|
+
for (let row = rect.top; row < rect.bottom; row++) {
|
|
2566
|
+
let rowContent = [];
|
|
2567
|
+
|
|
2568
|
+
for (let index = row * map.width + rect.left, col = rect.left; col < rect.right; col++, index++) {
|
|
2569
|
+
let pos = map.map[index];
|
|
2570
|
+
|
|
2571
|
+
if (!seen[pos]) {
|
|
2572
|
+
seen[pos] = true;
|
|
2573
|
+
let cellRect = map.findCell(pos),
|
|
2574
|
+
cell = table.nodeAt(pos);
|
|
2575
|
+
let extraLeft = rect.left - cellRect.left,
|
|
2576
|
+
extraRight = cellRect.right - rect.right;
|
|
2577
|
+
|
|
2578
|
+
if (extraLeft > 0 || extraRight > 0) {
|
|
2579
|
+
let attrs = cell.attrs;
|
|
2580
|
+
if (extraLeft > 0) attrs = removeColSpan(attrs, 0, extraLeft);
|
|
2581
|
+
if (extraRight > 0) attrs = removeColSpan(attrs, attrs.colspan - extraRight, extraRight);
|
|
2582
|
+
if (cellRect.left < rect.left) cell = cell.type.createAndFill(attrs);else cell = cell.type.create(attrs, cell.content);
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
if (cellRect.top < rect.top || cellRect.bottom > rect.bottom) {
|
|
2586
|
+
let attrs = setAttr(cell.attrs, 'rowspan', Math.min(cellRect.bottom, rect.bottom) - Math.max(cellRect.top, rect.top));
|
|
2587
|
+
if (cellRect.top < rect.top) cell = cell.type.createAndFill(attrs);else cell = cell.type.create(attrs, cell.content);
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
rowContent.push(cell);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
rows.push(table.child(row).copy(prosemirrorModel.Fragment.from(rowContent)));
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
const fragment = this.isColSelection() && this.isRowSelection() ? table : rows;
|
|
2598
|
+
return new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(fragment), 1, 1);
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
replace(tr, content = prosemirrorModel.Slice.empty) {
|
|
2602
|
+
let mapFrom = tr.steps.length,
|
|
2603
|
+
ranges = this.ranges;
|
|
2604
|
+
|
|
2605
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
2606
|
+
let {
|
|
2607
|
+
$from,
|
|
2608
|
+
$to
|
|
2609
|
+
} = ranges[i],
|
|
2610
|
+
mapping = tr.mapping.slice(mapFrom);
|
|
2611
|
+
tr.replace(mapping.map($from.pos), mapping.map($to.pos), i ? prosemirrorModel.Slice.empty : content);
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
let sel = prosemirrorState.Selection.findFrom(tr.doc.resolve(tr.mapping.slice(mapFrom).map(this.to)), -1);
|
|
2615
|
+
if (sel) tr.setSelection(sel);
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
replaceWith(tr, node) {
|
|
2619
|
+
this.replace(tr, new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(node), 0, 0));
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
forEachCell(f) {
|
|
2623
|
+
let table = this.$anchorCell.node(-1),
|
|
2624
|
+
map = TableMap.get(table),
|
|
2625
|
+
start = this.$anchorCell.start(-1);
|
|
2626
|
+
let cells = map.cellsInRect(map.rectBetween(this.$anchorCell.pos - start, this.$headCell.pos - start));
|
|
2627
|
+
|
|
2628
|
+
for (let i = 0; i < cells.length; i++) f(table.nodeAt(cells[i]), start + cells[i]);
|
|
2629
|
+
} // :: () → bool
|
|
2630
|
+
// True if this selection goes all the way from the top to the
|
|
2631
|
+
// bottom of the table.
|
|
2632
|
+
|
|
2633
|
+
|
|
2634
|
+
isColSelection() {
|
|
2635
|
+
let anchorTop = this.$anchorCell.index(-1),
|
|
2636
|
+
headTop = this.$headCell.index(-1);
|
|
2637
|
+
if (Math.min(anchorTop, headTop) > 0) return false;
|
|
2638
|
+
let anchorBot = anchorTop + this.$anchorCell.nodeAfter.attrs.rowspan,
|
|
2639
|
+
headBot = headTop + this.$headCell.nodeAfter.attrs.rowspan;
|
|
2640
|
+
return Math.max(anchorBot, headBot) == this.$headCell.node(-1).childCount;
|
|
2641
|
+
} // :: (ResolvedPos, ?ResolvedPos) → CellSelection
|
|
2642
|
+
// Returns the smallest column selection that covers the given anchor
|
|
2643
|
+
// and head cell.
|
|
2644
|
+
|
|
2645
|
+
|
|
2646
|
+
static colSelection($anchorCell, $headCell = $anchorCell) {
|
|
2647
|
+
let map = TableMap.get($anchorCell.node(-1)),
|
|
2648
|
+
start = $anchorCell.start(-1);
|
|
2649
|
+
let anchorRect = map.findCell($anchorCell.pos - start),
|
|
2650
|
+
headRect = map.findCell($headCell.pos - start);
|
|
2651
|
+
let doc = $anchorCell.node(0);
|
|
2652
|
+
|
|
2653
|
+
if (anchorRect.top <= headRect.top) {
|
|
2654
|
+
if (anchorRect.top > 0) $anchorCell = doc.resolve(start + map.map[anchorRect.left]);
|
|
2655
|
+
if (headRect.bottom < map.height) $headCell = doc.resolve(start + map.map[map.width * (map.height - 1) + headRect.right - 1]);
|
|
2656
|
+
} else {
|
|
2657
|
+
if (headRect.top > 0) $headCell = doc.resolve(start + map.map[headRect.left]);
|
|
2658
|
+
if (anchorRect.bottom < map.height) $anchorCell = doc.resolve(start + map.map[map.width * (map.height - 1) + anchorRect.right - 1]);
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
return new CellSelection($anchorCell, $headCell);
|
|
2662
|
+
} // :: () → bool
|
|
2663
|
+
// True if this selection goes all the way from the left to the
|
|
2664
|
+
// right of the table.
|
|
2665
|
+
|
|
2666
|
+
|
|
2667
|
+
isRowSelection() {
|
|
2668
|
+
let map = TableMap.get(this.$anchorCell.node(-1)),
|
|
2669
|
+
start = this.$anchorCell.start(-1);
|
|
2670
|
+
let anchorLeft = map.colCount(this.$anchorCell.pos - start),
|
|
2671
|
+
headLeft = map.colCount(this.$headCell.pos - start);
|
|
2672
|
+
if (Math.min(anchorLeft, headLeft) > 0) return false;
|
|
2673
|
+
let anchorRight = anchorLeft + this.$anchorCell.nodeAfter.attrs.colspan,
|
|
2674
|
+
headRight = headLeft + this.$headCell.nodeAfter.attrs.colspan;
|
|
2675
|
+
return Math.max(anchorRight, headRight) == map.width;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
eq(other) {
|
|
2679
|
+
return other instanceof CellSelection && other.$anchorCell.pos == this.$anchorCell.pos && other.$headCell.pos == this.$headCell.pos;
|
|
2680
|
+
} // :: (ResolvedPos, ?ResolvedPos) → CellSelection
|
|
2681
|
+
// Returns the smallest row selection that covers the given anchor
|
|
2682
|
+
// and head cell.
|
|
2683
|
+
|
|
2684
|
+
|
|
2685
|
+
static rowSelection($anchorCell, $headCell = $anchorCell) {
|
|
2686
|
+
let map = TableMap.get($anchorCell.node(-1)),
|
|
2687
|
+
start = $anchorCell.start(-1);
|
|
2688
|
+
let anchorRect = map.findCell($anchorCell.pos - start),
|
|
2689
|
+
headRect = map.findCell($headCell.pos - start);
|
|
2690
|
+
let doc = $anchorCell.node(0);
|
|
2691
|
+
|
|
2692
|
+
if (anchorRect.left <= headRect.left) {
|
|
2693
|
+
if (anchorRect.left > 0) $anchorCell = doc.resolve(start + map.map[anchorRect.top * map.width]);
|
|
2694
|
+
if (headRect.right < map.width) $headCell = doc.resolve(start + map.map[map.width * (headRect.top + 1) - 1]);
|
|
2695
|
+
} else {
|
|
2696
|
+
if (headRect.left > 0) $headCell = doc.resolve(start + map.map[headRect.top * map.width]);
|
|
2697
|
+
if (anchorRect.right < map.width) $anchorCell = doc.resolve(start + map.map[map.width * (anchorRect.top + 1) - 1]);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
return new CellSelection($anchorCell, $headCell);
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
toJSON() {
|
|
2704
|
+
return {
|
|
2705
|
+
type: 'cell',
|
|
2706
|
+
anchor: this.$anchorCell.pos,
|
|
2707
|
+
head: this.$headCell.pos
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
static fromJSON(doc, json) {
|
|
2712
|
+
return new CellSelection(doc.resolve(json.anchor), doc.resolve(json.head));
|
|
2713
|
+
} // :: (Node, number, ?number) → CellSelection
|
|
2714
|
+
|
|
2715
|
+
|
|
2716
|
+
static create(doc, anchorCell, headCell = anchorCell) {
|
|
2717
|
+
return new CellSelection(doc.resolve(anchorCell), doc.resolve(headCell));
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
getBookmark() {
|
|
2721
|
+
return new CellBookmark(this.$anchorCell.pos, this.$headCell.pos);
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
}
|
|
2725
|
+
CellSelection.prototype.visible = false;
|
|
2726
|
+
prosemirrorState.Selection.jsonID('cell', CellSelection);
|
|
2727
|
+
|
|
2728
|
+
class CellBookmark {
|
|
2729
|
+
constructor(anchor, head) {
|
|
2730
|
+
this.anchor = anchor;
|
|
2731
|
+
this.head = head;
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
map(mapping) {
|
|
2735
|
+
return new CellBookmark(mapping.map(this.anchor), mapping.map(this.head));
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
resolve(doc) {
|
|
2739
|
+
let $anchorCell = doc.resolve(this.anchor),
|
|
2740
|
+
$headCell = doc.resolve(this.head);
|
|
2741
|
+
if ($anchorCell.parent.type.spec.tableRole == 'row' && $headCell.parent.type.spec.tableRole == 'row' && $anchorCell.index() < $anchorCell.parent.childCount && $headCell.index() < $headCell.parent.childCount && inSameTable($anchorCell, $headCell)) return new CellSelection($anchorCell, $headCell);else return prosemirrorState.Selection.near($headCell, 1);
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
function drawCellSelection(state) {
|
|
2747
|
+
if (!(state.selection instanceof CellSelection)) return null;
|
|
2748
|
+
let cells = [];
|
|
2749
|
+
state.selection.forEachCell((node, pos) => {
|
|
2750
|
+
cells.push(prosemirrorView.Decoration.node(pos, pos + node.nodeSize, {
|
|
2751
|
+
class: 'selectedCell'
|
|
2752
|
+
}));
|
|
2753
|
+
});
|
|
2754
|
+
return prosemirrorView.DecorationSet.create(state.doc, cells);
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
function isCellBoundarySelection({
|
|
2758
|
+
$from,
|
|
2759
|
+
$to
|
|
2760
|
+
}) {
|
|
2761
|
+
if ($from.pos == $to.pos || $from.pos < $from.pos - 6) return false; // Cheap elimination
|
|
2762
|
+
|
|
2763
|
+
let afterFrom = $from.pos,
|
|
2764
|
+
beforeTo = $to.pos,
|
|
2765
|
+
depth = $from.depth;
|
|
2766
|
+
|
|
2767
|
+
for (; depth >= 0; depth--, afterFrom++) if ($from.after(depth + 1) < $from.end(depth)) break;
|
|
2768
|
+
|
|
2769
|
+
for (let d = $to.depth; d >= 0; d--, beforeTo--) if ($to.before(d + 1) > $to.start(d)) break;
|
|
2770
|
+
|
|
2771
|
+
return afterFrom == beforeTo && /row|table/.test($from.node(depth).type.spec.tableRole);
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
function isTextSelectionAcrossCells({
|
|
2775
|
+
$from,
|
|
2776
|
+
$to
|
|
2777
|
+
}) {
|
|
2778
|
+
let fromCellBoundaryNode;
|
|
2779
|
+
let toCellBoundaryNode;
|
|
2780
|
+
|
|
2781
|
+
for (let i = $from.depth; i > 0; i--) {
|
|
2782
|
+
let node = $from.node(i);
|
|
2783
|
+
|
|
2784
|
+
if (node.type.spec.tableRole === 'cell' || node.type.spec.tableRole === 'header_cell') {
|
|
2785
|
+
fromCellBoundaryNode = node;
|
|
2786
|
+
break;
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
for (let i = $to.depth; i > 0; i--) {
|
|
2791
|
+
let node = $to.node(i);
|
|
2792
|
+
|
|
2793
|
+
if (node.type.spec.tableRole === 'cell' || node.type.spec.tableRole === 'header_cell') {
|
|
2794
|
+
toCellBoundaryNode = node;
|
|
2795
|
+
break;
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
return fromCellBoundaryNode !== toCellBoundaryNode && $to.parentOffset === 0;
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
function normalizeSelection(state, tr, allowTableNodeSelection) {
|
|
2803
|
+
let sel = (tr || state).selection,
|
|
2804
|
+
doc = (tr || state).doc,
|
|
2805
|
+
normalize,
|
|
2806
|
+
role;
|
|
2807
|
+
|
|
2808
|
+
if (sel instanceof prosemirrorState.NodeSelection && (role = sel.node.type.spec.tableRole)) {
|
|
2809
|
+
if (role == 'cell' || role == 'header_cell') {
|
|
2810
|
+
normalize = CellSelection.create(doc, sel.from);
|
|
2811
|
+
} else if (role == 'row') {
|
|
2812
|
+
let $cell = doc.resolve(sel.from + 1);
|
|
2813
|
+
normalize = CellSelection.rowSelection($cell, $cell);
|
|
2814
|
+
} else if (!allowTableNodeSelection) {
|
|
2815
|
+
let map = TableMap.get(sel.node),
|
|
2816
|
+
start = sel.from + 1;
|
|
2817
|
+
let lastCell = start + map.map[map.width * map.height - 1];
|
|
2818
|
+
normalize = CellSelection.create(doc, start + 1, lastCell);
|
|
2819
|
+
}
|
|
2820
|
+
} else if (sel instanceof prosemirrorState.TextSelection && isCellBoundarySelection(sel)) {
|
|
2821
|
+
normalize = prosemirrorState.TextSelection.create(doc, sel.from);
|
|
2822
|
+
} else if (sel instanceof prosemirrorState.TextSelection && isTextSelectionAcrossCells(sel)) {
|
|
2823
|
+
normalize = prosemirrorState.TextSelection.create(doc, sel.$from.start(), sel.$from.end());
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
if (normalize) (tr || (tr = state.tr)).setSelection(normalize);
|
|
2827
|
+
return tr;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
// Utilities used for copy/paste handling.
|
|
2831
|
+
// : (Slice) → ?{width: number, height: number, rows: [Fragment]}
|
|
2832
|
+
// Get a rectangular area of cells from a slice, or null if the outer
|
|
2833
|
+
// nodes of the slice aren't table cells or rows.
|
|
2834
|
+
|
|
2835
|
+
function pastedCells(slice) {
|
|
2836
|
+
if (!slice.size) return null;
|
|
2837
|
+
let {
|
|
2838
|
+
content,
|
|
2839
|
+
openStart,
|
|
2840
|
+
openEnd
|
|
2841
|
+
} = slice;
|
|
2842
|
+
|
|
2843
|
+
while (content.childCount == 1 && (openStart > 0 && openEnd > 0 || content.firstChild.type.spec.tableRole == 'table')) {
|
|
2844
|
+
openStart--;
|
|
2845
|
+
openEnd--;
|
|
2846
|
+
content = content.firstChild.content;
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
let first = content.firstChild,
|
|
2850
|
+
role = first.type.spec.tableRole;
|
|
2851
|
+
let schema = first.type.schema,
|
|
2852
|
+
rows = [];
|
|
2853
|
+
|
|
2854
|
+
if (role == 'row') {
|
|
2855
|
+
for (let i = 0; i < content.childCount; i++) {
|
|
2856
|
+
let cells = content.child(i).content;
|
|
2857
|
+
let left = i ? 0 : Math.max(0, openStart - 1);
|
|
2858
|
+
let right = i < content.childCount - 1 ? 0 : Math.max(0, openEnd - 1);
|
|
2859
|
+
if (left || right) cells = fitSlice(tableNodeTypes(schema).row, new prosemirrorModel.Slice(cells, left, right)).content;
|
|
2860
|
+
rows.push(cells);
|
|
2861
|
+
}
|
|
2862
|
+
} else if (role == 'cell' || role == 'header_cell') {
|
|
2863
|
+
rows.push(openStart || openEnd ? fitSlice(tableNodeTypes(schema).row, new prosemirrorModel.Slice(content, openStart, openEnd)).content : content);
|
|
2864
|
+
} else {
|
|
2865
|
+
return null;
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
return ensureRectangular(schema, rows);
|
|
2869
|
+
} // : (Schema, [Fragment]) → {width: number, height: number, rows: [Fragment]}
|
|
2870
|
+
// Compute the width and height of a set of cells, and make sure each
|
|
2871
|
+
// row has the same number of cells.
|
|
2872
|
+
|
|
2873
|
+
function ensureRectangular(schema, rows) {
|
|
2874
|
+
let widths = [];
|
|
2875
|
+
|
|
2876
|
+
for (let i = 0; i < rows.length; i++) {
|
|
2877
|
+
let row = rows[i];
|
|
2878
|
+
|
|
2879
|
+
for (let j = row.childCount - 1; j >= 0; j--) {
|
|
2880
|
+
let {
|
|
2881
|
+
rowspan,
|
|
2882
|
+
colspan
|
|
2883
|
+
} = row.child(j).attrs;
|
|
2884
|
+
|
|
2885
|
+
for (let r = i; r < i + rowspan; r++) widths[r] = (widths[r] || 0) + colspan;
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
let width = 0;
|
|
2890
|
+
|
|
2891
|
+
for (let r = 0; r < widths.length; r++) width = Math.max(width, widths[r]);
|
|
2892
|
+
|
|
2893
|
+
for (let r = 0; r < widths.length; r++) {
|
|
2894
|
+
if (r >= rows.length) rows.push(prosemirrorModel.Fragment.empty);
|
|
2895
|
+
|
|
2896
|
+
if (widths[r] < width) {
|
|
2897
|
+
let empty = tableNodeTypes(schema).cell.createAndFill(),
|
|
2898
|
+
cells = [];
|
|
2899
|
+
|
|
2900
|
+
for (let i = widths[r]; i < width; i++) cells.push(empty);
|
|
2901
|
+
|
|
2902
|
+
rows[r] = rows[r].append(prosemirrorModel.Fragment.from(cells));
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
return {
|
|
2907
|
+
height: rows.length,
|
|
2908
|
+
width,
|
|
2909
|
+
rows
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
function fitSlice(nodeType, slice) {
|
|
2914
|
+
let node = nodeType.createAndFill();
|
|
2915
|
+
let tr = new Transform(node).replace(0, node.content.size, slice);
|
|
2916
|
+
return tr.doc;
|
|
2917
|
+
} // : ({width: number, height: number, rows: [Fragment]}, number, number) → {width: number, height: number, rows: [Fragment]}
|
|
2918
|
+
// Clip or extend (repeat) the given set of cells to cover the given
|
|
2919
|
+
// width and height. Will clip rowspan/colspan cells at the edges when
|
|
2920
|
+
// they stick out.
|
|
2921
|
+
|
|
2922
|
+
function clipCells({
|
|
2923
|
+
width,
|
|
2924
|
+
height,
|
|
2925
|
+
rows
|
|
2926
|
+
}, newWidth, newHeight) {
|
|
2927
|
+
if (width != newWidth) {
|
|
2928
|
+
let added = [],
|
|
2929
|
+
newRows = [];
|
|
2930
|
+
|
|
2931
|
+
for (let row = 0; row < rows.length; row++) {
|
|
2932
|
+
let frag = rows[row],
|
|
2933
|
+
cells = [];
|
|
2934
|
+
|
|
2935
|
+
for (let col = added[row] || 0, i = 0; col < newWidth; i++) {
|
|
2936
|
+
let cell = frag.child(i % frag.childCount);
|
|
2937
|
+
if (col + cell.attrs.colspan > newWidth) cell = cell.type.create(removeColSpan(cell.attrs, cell.attrs.colspan, col + cell.attrs.colspan - newWidth), cell.content);
|
|
2938
|
+
cells.push(cell);
|
|
2939
|
+
col += cell.attrs.colspan;
|
|
2940
|
+
|
|
2941
|
+
for (let j = 1; j < cell.attrs.rowspan; j++) added[row + j] = (added[row + j] || 0) + cell.attrs.colspan;
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
newRows.push(prosemirrorModel.Fragment.from(cells));
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
rows = newRows;
|
|
2948
|
+
width = newWidth;
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
if (height != newHeight) {
|
|
2952
|
+
let newRows = [];
|
|
2953
|
+
|
|
2954
|
+
for (let row = 0, i = 0; row < newHeight; row++, i++) {
|
|
2955
|
+
let cells = [],
|
|
2956
|
+
source = rows[i % height];
|
|
2957
|
+
|
|
2958
|
+
for (let j = 0; j < source.childCount; j++) {
|
|
2959
|
+
let cell = source.child(j);
|
|
2960
|
+
if (row + cell.attrs.rowspan > newHeight) cell = cell.type.create(setAttr(cell.attrs, 'rowspan', Math.max(1, newHeight - cell.attrs.rowspan)), cell.content);
|
|
2961
|
+
cells.push(cell);
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
newRows.push(prosemirrorModel.Fragment.from(cells));
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
rows = newRows;
|
|
2968
|
+
height = newHeight;
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
return {
|
|
2972
|
+
width,
|
|
2973
|
+
height,
|
|
2974
|
+
rows
|
|
2975
|
+
};
|
|
2976
|
+
} // Make sure a table has at least the given width and height. Return
|
|
2977
|
+
// true if something was changed.
|
|
2978
|
+
|
|
2979
|
+
function growTable(tr, map, table, start, width, height, mapFrom) {
|
|
2980
|
+
let schema = tr.doc.type.schema,
|
|
2981
|
+
types = tableNodeTypes(schema),
|
|
2982
|
+
empty,
|
|
2983
|
+
emptyHead;
|
|
2984
|
+
|
|
2985
|
+
if (width > map.width) {
|
|
2986
|
+
for (let row = 0, rowEnd = 0; row < map.height; row++) {
|
|
2987
|
+
let rowNode = table.child(row);
|
|
2988
|
+
rowEnd += rowNode.nodeSize;
|
|
2989
|
+
let cells = [],
|
|
2990
|
+
add;
|
|
2991
|
+
if (rowNode.lastChild == null || rowNode.lastChild.type == types.cell) add = empty || (empty = types.cell.createAndFill());else add = emptyHead || (emptyHead = types.header_cell.createAndFill());
|
|
2992
|
+
|
|
2993
|
+
for (let i = map.width; i < width; i++) cells.push(add);
|
|
2994
|
+
|
|
2995
|
+
tr.insert(tr.mapping.slice(mapFrom).map(rowEnd - 1 + start), cells);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
if (height > map.height) {
|
|
3000
|
+
let cells = [];
|
|
3001
|
+
|
|
3002
|
+
for (let i = 0, start = (map.height - 1) * map.width; i < Math.max(map.width, width); i++) {
|
|
3003
|
+
let header = i >= map.width ? false : table.nodeAt(map.map[start + i]).type == types.header_cell;
|
|
3004
|
+
cells.push(header ? emptyHead || (emptyHead = types.header_cell.createAndFill()) : empty || (empty = types.cell.createAndFill()));
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
let emptyRow = types.row.create(null, prosemirrorModel.Fragment.from(cells)),
|
|
3008
|
+
rows = [];
|
|
3009
|
+
|
|
3010
|
+
for (let i = map.height; i < height; i++) rows.push(emptyRow);
|
|
3011
|
+
|
|
3012
|
+
tr.insert(tr.mapping.slice(mapFrom).map(start + table.nodeSize - 2), rows);
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
return !!(empty || emptyHead);
|
|
3016
|
+
} // Make sure the given line (left, top) to (right, top) doesn't cross
|
|
3017
|
+
// any rowspan cells by splitting cells that cross it. Return true if
|
|
3018
|
+
// something changed.
|
|
3019
|
+
|
|
3020
|
+
|
|
3021
|
+
function isolateHorizontal(tr, map, table, start, left, right, top, mapFrom) {
|
|
3022
|
+
if (top == 0 || top == map.height) return false;
|
|
3023
|
+
let found = false;
|
|
3024
|
+
|
|
3025
|
+
for (let col = left; col < right; col++) {
|
|
3026
|
+
let index = top * map.width + col,
|
|
3027
|
+
pos = map.map[index];
|
|
3028
|
+
|
|
3029
|
+
if (map.map[index - map.width] == pos) {
|
|
3030
|
+
found = true;
|
|
3031
|
+
let cell = table.nodeAt(pos);
|
|
3032
|
+
let {
|
|
3033
|
+
top: cellTop,
|
|
3034
|
+
left: cellLeft
|
|
3035
|
+
} = map.findCell(pos);
|
|
3036
|
+
tr.setNodeMarkup(tr.mapping.slice(mapFrom).map(pos + start), null, setAttr(cell.attrs, 'rowspan', top - cellTop));
|
|
3037
|
+
tr.insert(tr.mapping.slice(mapFrom).map(map.positionAt(top, cellLeft, table)), cell.type.createAndFill(setAttr(cell.attrs, 'rowspan', cellTop + cell.attrs.rowspan - top)));
|
|
3038
|
+
col += cell.attrs.colspan - 1;
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
return found;
|
|
3043
|
+
} // Make sure the given line (left, top) to (left, bottom) doesn't
|
|
3044
|
+
// cross any colspan cells by splitting cells that cross it. Return
|
|
3045
|
+
// true if something changed.
|
|
3046
|
+
|
|
3047
|
+
|
|
3048
|
+
function isolateVertical(tr, map, table, start, top, bottom, left, mapFrom) {
|
|
3049
|
+
if (left == 0 || left == map.width) return false;
|
|
3050
|
+
let found = false;
|
|
3051
|
+
|
|
3052
|
+
for (let row = top; row < bottom; row++) {
|
|
3053
|
+
let index = row * map.width + left,
|
|
3054
|
+
pos = map.map[index];
|
|
3055
|
+
|
|
3056
|
+
if (map.map[index - 1] == pos) {
|
|
3057
|
+
found = true;
|
|
3058
|
+
let cell = table.nodeAt(pos),
|
|
3059
|
+
cellLeft = map.colCount(pos);
|
|
3060
|
+
let updatePos = tr.mapping.slice(mapFrom).map(pos + start);
|
|
3061
|
+
tr.setNodeMarkup(updatePos, null, removeColSpan(cell.attrs, left - cellLeft, cell.attrs.colspan - (left - cellLeft)));
|
|
3062
|
+
tr.insert(updatePos + cell.nodeSize, cell.type.createAndFill(removeColSpan(cell.attrs, 0, left - cellLeft)));
|
|
3063
|
+
row += cell.attrs.rowspan - 1;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
return found;
|
|
3068
|
+
} // Insert the given set of cells (as returned by `pastedCells`) into a
|
|
3069
|
+
// table, at the position pointed at by rect.
|
|
3070
|
+
|
|
3071
|
+
|
|
3072
|
+
function insertCells(state, dispatch, tableStart, rect, cells) {
|
|
3073
|
+
let table = tableStart ? state.doc.nodeAt(tableStart - 1) : state.doc,
|
|
3074
|
+
map = TableMap.get(table);
|
|
3075
|
+
let {
|
|
3076
|
+
top,
|
|
3077
|
+
left
|
|
3078
|
+
} = rect;
|
|
3079
|
+
let right = left + cells.width,
|
|
3080
|
+
bottom = top + cells.height;
|
|
3081
|
+
let tr = state.tr,
|
|
3082
|
+
mapFrom = 0;
|
|
3083
|
+
|
|
3084
|
+
function recomp() {
|
|
3085
|
+
table = tableStart ? tr.doc.nodeAt(tableStart - 1) : tr.doc;
|
|
3086
|
+
map = TableMap.get(table);
|
|
3087
|
+
mapFrom = tr.mapping.maps.length;
|
|
3088
|
+
} // Prepare the table to be large enough and not have any cells
|
|
3089
|
+
// crossing the boundaries of the rectangle that we want to
|
|
3090
|
+
// insert into. If anything about it changes, recompute the table
|
|
3091
|
+
// map so that subsequent operations can see the current shape.
|
|
3092
|
+
|
|
3093
|
+
|
|
3094
|
+
if (growTable(tr, map, table, tableStart, right, bottom, mapFrom)) recomp();
|
|
3095
|
+
if (isolateHorizontal(tr, map, table, tableStart, left, right, top, mapFrom)) recomp();
|
|
3096
|
+
if (isolateHorizontal(tr, map, table, tableStart, left, right, bottom, mapFrom)) recomp();
|
|
3097
|
+
if (isolateVertical(tr, map, table, tableStart, top, bottom, left, mapFrom)) recomp();
|
|
3098
|
+
if (isolateVertical(tr, map, table, tableStart, top, bottom, right, mapFrom)) recomp();
|
|
3099
|
+
|
|
3100
|
+
for (let row = top; row < bottom; row++) {
|
|
3101
|
+
let from = map.positionAt(row, left, table),
|
|
3102
|
+
to = map.positionAt(row, right, table);
|
|
3103
|
+
tr.replace(tr.mapping.slice(mapFrom).map(from + tableStart), tr.mapping.slice(mapFrom).map(to + tableStart), new prosemirrorModel.Slice(cells.rows[row - top], 0, 0));
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
recomp();
|
|
3107
|
+
tr.setSelection(new CellSelection(tr.doc.resolve(tableStart + map.positionAt(top, left, table)), tr.doc.resolve(tableStart + map.positionAt(bottom - 1, right - 1, table))));
|
|
3108
|
+
dispatch(tr);
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
// This file defines a number of helpers for wiring up user input to
|
|
3112
|
+
const handleKeyDown = keydownHandler({
|
|
3113
|
+
ArrowLeft: arrow('horiz', -1),
|
|
3114
|
+
ArrowRight: arrow('horiz', 1),
|
|
3115
|
+
ArrowUp: arrow('vert', -1),
|
|
3116
|
+
ArrowDown: arrow('vert', 1),
|
|
3117
|
+
'Shift-ArrowLeft': shiftArrow('horiz', -1),
|
|
3118
|
+
'Shift-ArrowRight': shiftArrow('horiz', 1),
|
|
3119
|
+
'Shift-ArrowUp': shiftArrow('vert', -1),
|
|
3120
|
+
'Shift-ArrowDown': shiftArrow('vert', 1),
|
|
3121
|
+
Backspace: deleteCellSelection,
|
|
3122
|
+
'Mod-Backspace': deleteCellSelection,
|
|
3123
|
+
Delete: deleteCellSelection,
|
|
3124
|
+
'Mod-Delete': deleteCellSelection
|
|
3125
|
+
});
|
|
3126
|
+
|
|
3127
|
+
function maybeSetSelection(state, dispatch, selection) {
|
|
3128
|
+
if (selection.eq(state.selection)) return false;
|
|
3129
|
+
if (dispatch) dispatch(state.tr.setSelection(selection).scrollIntoView());
|
|
3130
|
+
return true;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
function arrow(axis, dir) {
|
|
3134
|
+
return (state, dispatch, view) => {
|
|
3135
|
+
let sel = state.selection;
|
|
3136
|
+
|
|
3137
|
+
if (sel instanceof CellSelection) {
|
|
3138
|
+
return maybeSetSelection(state, dispatch, prosemirrorState.Selection.near(sel.$headCell, dir));
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
if (axis != 'horiz' && !sel.empty) return false;
|
|
3142
|
+
let end = atEndOfCell(view, axis, dir);
|
|
3143
|
+
if (end == null) return false;
|
|
3144
|
+
|
|
3145
|
+
if (axis == 'horiz') {
|
|
3146
|
+
return maybeSetSelection(state, dispatch, prosemirrorState.Selection.near(state.doc.resolve(sel.head + dir), dir));
|
|
3147
|
+
} else {
|
|
3148
|
+
let $cell = state.doc.resolve(end),
|
|
3149
|
+
$next = nextCell($cell, axis, dir),
|
|
3150
|
+
newSel;
|
|
3151
|
+
if ($next) newSel = prosemirrorState.Selection.near($next, 1);else if (dir < 0) newSel = prosemirrorState.Selection.near(state.doc.resolve($cell.before(-1)), -1);else newSel = prosemirrorState.Selection.near(state.doc.resolve($cell.after(-1)), 1);
|
|
3152
|
+
return maybeSetSelection(state, dispatch, newSel);
|
|
3153
|
+
}
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
function shiftArrow(axis, dir) {
|
|
3158
|
+
return (state, dispatch, view) => {
|
|
3159
|
+
let sel = state.selection;
|
|
3160
|
+
|
|
3161
|
+
if (!(sel instanceof CellSelection)) {
|
|
3162
|
+
let end = atEndOfCell(view, axis, dir);
|
|
3163
|
+
if (end == null) return false;
|
|
3164
|
+
sel = new CellSelection(state.doc.resolve(end));
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
let $head = nextCell(sel.$headCell, axis, dir);
|
|
3168
|
+
if (!$head) return false;
|
|
3169
|
+
return maybeSetSelection(state, dispatch, new CellSelection(sel.$anchorCell, $head));
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
function deleteCellSelection(state, dispatch) {
|
|
3174
|
+
let sel = state.selection;
|
|
3175
|
+
if (!(sel instanceof CellSelection)) return false;
|
|
3176
|
+
|
|
3177
|
+
if (dispatch) {
|
|
3178
|
+
let tr = state.tr,
|
|
3179
|
+
baseContent = tableNodeTypes(state.schema).cell.createAndFill().content;
|
|
3180
|
+
sel.forEachCell((cell, pos) => {
|
|
3181
|
+
if (!cell.content.eq(baseContent)) tr.replace(tr.mapping.map(pos + 1), tr.mapping.map(pos + cell.nodeSize - 1), new prosemirrorModel.Slice(baseContent, 0, 0));
|
|
3182
|
+
});
|
|
3183
|
+
if (tr.docChanged) dispatch(tr);
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
return true;
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3189
|
+
function handleTripleClick(view, pos) {
|
|
3190
|
+
let doc = view.state.doc,
|
|
3191
|
+
$cell = cellAround(doc.resolve(pos));
|
|
3192
|
+
if (!$cell) return false;
|
|
3193
|
+
view.dispatch(view.state.tr.setSelection(new CellSelection($cell)));
|
|
3194
|
+
return true;
|
|
3195
|
+
}
|
|
3196
|
+
function handlePaste(view, _, slice) {
|
|
3197
|
+
if (!isInTable(view.state)) return false;
|
|
3198
|
+
let cells = pastedCells(slice),
|
|
3199
|
+
sel = view.state.selection;
|
|
3200
|
+
|
|
3201
|
+
if (sel instanceof CellSelection) {
|
|
3202
|
+
if (!cells) cells = {
|
|
3203
|
+
width: 1,
|
|
3204
|
+
height: 1,
|
|
3205
|
+
rows: [prosemirrorModel.Fragment.from(fitSlice(tableNodeTypes(view.state.schema).cell, slice))]
|
|
3206
|
+
};
|
|
3207
|
+
let table = sel.$anchorCell.node(-1),
|
|
3208
|
+
start = sel.$anchorCell.start(-1);
|
|
3209
|
+
let rect = TableMap.get(table).rectBetween(sel.$anchorCell.pos - start, sel.$headCell.pos - start);
|
|
3210
|
+
cells = clipCells(cells, rect.right - rect.left, rect.bottom - rect.top);
|
|
3211
|
+
insertCells(view.state, view.dispatch, start, rect, cells);
|
|
3212
|
+
return true;
|
|
3213
|
+
} else if (cells) {
|
|
3214
|
+
let $cell = selectionCell(view.state),
|
|
3215
|
+
start = $cell.start(-1);
|
|
3216
|
+
insertCells(view.state, view.dispatch, start, TableMap.get($cell.node(-1)).findCell($cell.pos - start), cells);
|
|
3217
|
+
return true;
|
|
3218
|
+
} else {
|
|
3219
|
+
return false;
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
function handleMouseDown$1(view, startEvent) {
|
|
3223
|
+
if (startEvent.ctrlKey || startEvent.metaKey) return;
|
|
3224
|
+
let startDOMCell = domInCell(view, startEvent.target),
|
|
3225
|
+
$anchor;
|
|
3226
|
+
|
|
3227
|
+
if (startEvent.shiftKey && view.state.selection instanceof CellSelection) {
|
|
3228
|
+
// Adding to an existing cell selection
|
|
3229
|
+
setCellSelection(view.state.selection.$anchorCell, startEvent);
|
|
3230
|
+
startEvent.preventDefault();
|
|
3231
|
+
} else if (startEvent.shiftKey && startDOMCell && ($anchor = cellAround(view.state.selection.$anchor)) != null && cellUnderMouse(view, startEvent).pos != $anchor.pos) {
|
|
3232
|
+
// Adding to a selection that starts in another cell (causing a
|
|
3233
|
+
// cell selection to be created).
|
|
3234
|
+
setCellSelection($anchor, startEvent);
|
|
3235
|
+
startEvent.preventDefault();
|
|
3236
|
+
} else if (!startDOMCell) {
|
|
3237
|
+
// Not in a cell, let the default behavior happen.
|
|
3238
|
+
return;
|
|
3239
|
+
} // Create and dispatch a cell selection between the given anchor and
|
|
3240
|
+
// the position under the mouse.
|
|
3241
|
+
|
|
3242
|
+
|
|
3243
|
+
function setCellSelection($anchor, event) {
|
|
3244
|
+
let $head = cellUnderMouse(view, event);
|
|
3245
|
+
let starting = key$1.getState(view.state) == null;
|
|
3246
|
+
|
|
3247
|
+
if (!$head || !inSameTable($anchor, $head)) {
|
|
3248
|
+
if (starting) $head = $anchor;else return;
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
let selection = new CellSelection($anchor, $head);
|
|
3252
|
+
|
|
3253
|
+
if (starting || !view.state.selection.eq(selection)) {
|
|
3254
|
+
let tr = view.state.tr.setSelection(selection);
|
|
3255
|
+
if (starting) tr.setMeta(key$1, $anchor.pos);
|
|
3256
|
+
view.dispatch(tr);
|
|
3257
|
+
}
|
|
3258
|
+
} // Stop listening to mouse motion events.
|
|
3259
|
+
|
|
3260
|
+
|
|
3261
|
+
function stop() {
|
|
3262
|
+
view.root.removeEventListener('mouseup', stop);
|
|
3263
|
+
view.root.removeEventListener('dragstart', stop);
|
|
3264
|
+
view.root.removeEventListener('mousemove', move);
|
|
3265
|
+
if (key$1.getState(view.state) != null) view.dispatch(view.state.tr.setMeta(key$1, -1));
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
function move(event) {
|
|
3269
|
+
let anchor = key$1.getState(view.state),
|
|
3270
|
+
$anchor;
|
|
3271
|
+
|
|
3272
|
+
if (anchor != null) {
|
|
3273
|
+
// Continuing an existing cross-cell selection
|
|
3274
|
+
$anchor = view.state.doc.resolve(anchor);
|
|
3275
|
+
} else if (domInCell(view, event.target) != startDOMCell) {
|
|
3276
|
+
// Moving out of the initial cell -- start a new cell selection
|
|
3277
|
+
$anchor = cellUnderMouse(view, startEvent);
|
|
3278
|
+
if (!$anchor) return stop();
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
if ($anchor) setCellSelection($anchor, event);
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
view.root.addEventListener('mouseup', stop);
|
|
3285
|
+
view.root.addEventListener('dragstart', stop);
|
|
3286
|
+
view.root.addEventListener('mousemove', move);
|
|
3287
|
+
} // Check whether the cursor is at the end of a cell (so that further
|
|
3288
|
+
// motion would move out of the cell)
|
|
3289
|
+
|
|
3290
|
+
function atEndOfCell(view, axis, dir) {
|
|
3291
|
+
if (!(view.state.selection instanceof prosemirrorState.TextSelection)) return null;
|
|
3292
|
+
let {
|
|
3293
|
+
$head
|
|
3294
|
+
} = view.state.selection;
|
|
3295
|
+
|
|
3296
|
+
for (let d = $head.depth - 1; d >= 0; d--) {
|
|
3297
|
+
let parent = $head.node(d),
|
|
3298
|
+
index = dir < 0 ? $head.index(d) : $head.indexAfter(d);
|
|
3299
|
+
if (index != (dir < 0 ? 0 : parent.childCount)) return null;
|
|
3300
|
+
|
|
3301
|
+
if (parent.type.spec.tableRole == 'cell' || parent.type.spec.tableRole == 'header_cell') {
|
|
3302
|
+
let cellPos = $head.before(d);
|
|
3303
|
+
let dirStr = axis == 'vert' ? dir > 0 ? 'down' : 'up' : dir > 0 ? 'right' : 'left';
|
|
3304
|
+
return view.endOfTextblock(dirStr) ? cellPos : null;
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
return null;
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
function domInCell(view, dom) {
|
|
3312
|
+
for (; dom && dom != view.dom; dom = dom.parentNode) if (dom.nodeName == 'TD' || dom.nodeName == 'TH') return dom;
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
function cellUnderMouse(view, event) {
|
|
3316
|
+
let mousePos = view.posAtCoords({
|
|
3317
|
+
left: event.clientX,
|
|
3318
|
+
top: event.clientY
|
|
3319
|
+
});
|
|
3320
|
+
if (!mousePos) return null;
|
|
3321
|
+
return mousePos ? cellAround(view.state.doc.resolve(mousePos.pos)) : null;
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
// This file defines helpers for normalizing tables, making sure no
|
|
3325
|
+
const fixTablesKey = new prosemirrorState.PluginKey('fix-tables'); // Helper for iterating through the nodes in a document that changed
|
|
3326
|
+
// compared to the given previous document. Useful for avoiding
|
|
3327
|
+
// duplicate work on each transaction.
|
|
3328
|
+
|
|
3329
|
+
function changedDescendants(old, cur, offset, f) {
|
|
3330
|
+
let oldSize = old.childCount,
|
|
3331
|
+
curSize = cur.childCount;
|
|
3332
|
+
|
|
3333
|
+
outer: for (let i = 0, j = 0; i < curSize; i++) {
|
|
3334
|
+
let child = cur.child(i);
|
|
3335
|
+
|
|
3336
|
+
for (let scan = j, e = Math.min(oldSize, i + 3); scan < e; scan++) {
|
|
3337
|
+
if (old.child(scan) == child) {
|
|
3338
|
+
j = scan + 1;
|
|
3339
|
+
offset += child.nodeSize;
|
|
3340
|
+
continue outer;
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
f(child, offset);
|
|
3345
|
+
if (j < oldSize && old.child(j).sameMarkup(child)) changedDescendants(old.child(j), child, offset + 1, f);else child.nodesBetween(0, child.content.size, f, offset + 1);
|
|
3346
|
+
offset += child.nodeSize;
|
|
3347
|
+
}
|
|
3348
|
+
} // :: (EditorState, ?EditorState) → ?Transaction
|
|
3349
|
+
// Inspect all tables in the given state's document and return a
|
|
3350
|
+
// transaction that fixes them, if necessary. If `oldState` was
|
|
3351
|
+
// provided, that is assumed to hold a previous, known-good state,
|
|
3352
|
+
// which will be used to avoid re-scanning unchanged parts of the
|
|
3353
|
+
// document.
|
|
3354
|
+
|
|
3355
|
+
|
|
3356
|
+
function fixTables(state, oldState) {
|
|
3357
|
+
let tr,
|
|
3358
|
+
check = (node, pos) => {
|
|
3359
|
+
if (node.type.spec.tableRole == 'table') tr = fixTable(state, node, pos, tr);
|
|
3360
|
+
};
|
|
3361
|
+
|
|
3362
|
+
if (!oldState) state.doc.descendants(check);else if (oldState.doc != state.doc) changedDescendants(oldState.doc, state.doc, 0, check);
|
|
3363
|
+
return tr;
|
|
3364
|
+
} // : (EditorState, Node, number, ?Transaction) → ?Transaction
|
|
3365
|
+
// Fix the given table, if necessary. Will append to the transaction
|
|
3366
|
+
// it was given, if non-null, or create a new one if necessary.
|
|
3367
|
+
|
|
3368
|
+
function fixTable(state, table, tablePos, tr) {
|
|
3369
|
+
let map = TableMap.get(table);
|
|
3370
|
+
if (!map.problems) return tr;
|
|
3371
|
+
if (!tr) tr = state.tr; // Track which rows we must add cells to, so that we can adjust that
|
|
3372
|
+
// when fixing collisions.
|
|
3373
|
+
|
|
3374
|
+
let mustAdd = [];
|
|
3375
|
+
|
|
3376
|
+
for (let i = 0; i < map.height; i++) mustAdd.push(0);
|
|
3377
|
+
|
|
3378
|
+
for (let i = 0; i < map.problems.length; i++) {
|
|
3379
|
+
let prob = map.problems[i];
|
|
3380
|
+
|
|
3381
|
+
if (prob.type == 'collision') {
|
|
3382
|
+
let cell = table.nodeAt(prob.pos);
|
|
3383
|
+
|
|
3384
|
+
for (let j = 0; j < cell.attrs.rowspan; j++) mustAdd[prob.row + j] += prob.n;
|
|
3385
|
+
|
|
3386
|
+
tr.setNodeMarkup(tr.mapping.map(tablePos + 1 + prob.pos), null, removeColSpan(cell.attrs, cell.attrs.colspan - prob.n, prob.n));
|
|
3387
|
+
} else if (prob.type == 'missing') {
|
|
3388
|
+
mustAdd[prob.row] += prob.n;
|
|
3389
|
+
} else if (prob.type == 'overlong_rowspan') {
|
|
3390
|
+
let cell = table.nodeAt(prob.pos);
|
|
3391
|
+
tr.setNodeMarkup(tr.mapping.map(tablePos + 1 + prob.pos), null, setAttr(cell.attrs, 'rowspan', cell.attrs.rowspan - prob.n));
|
|
3392
|
+
} else if (prob.type == 'colwidth mismatch') {
|
|
3393
|
+
let cell = table.nodeAt(prob.pos);
|
|
3394
|
+
tr.setNodeMarkup(tr.mapping.map(tablePos + 1 + prob.pos), null, setAttr(cell.attrs, 'colwidth', prob.colwidth));
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
let first, last;
|
|
3399
|
+
|
|
3400
|
+
for (let i = 0; i < mustAdd.length; i++) if (mustAdd[i]) {
|
|
3401
|
+
if (first == null) first = i;
|
|
3402
|
+
last = i;
|
|
3403
|
+
} // Add the necessary cells, using a heuristic for whether to add the
|
|
3404
|
+
// cells at the start or end of the rows (if it looks like a 'bite'
|
|
3405
|
+
// was taken out of the table, add cells at the start of the row
|
|
3406
|
+
// after the bite. Otherwise add them at the end).
|
|
3407
|
+
|
|
3408
|
+
|
|
3409
|
+
for (let i = 0, pos = tablePos + 1; i < map.height; i++) {
|
|
3410
|
+
let row = table.child(i);
|
|
3411
|
+
let end = pos + row.nodeSize;
|
|
3412
|
+
let add = mustAdd[i];
|
|
3413
|
+
|
|
3414
|
+
if (add > 0) {
|
|
3415
|
+
let tableNodeType = 'cell';
|
|
3416
|
+
|
|
3417
|
+
if (row.firstChild) {
|
|
3418
|
+
tableNodeType = row.firstChild.type.spec.tableRole;
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
let nodes = [];
|
|
3422
|
+
|
|
3423
|
+
for (let j = 0; j < add; j++) nodes.push(tableNodeTypes(state.schema)[tableNodeType].createAndFill());
|
|
3424
|
+
|
|
3425
|
+
let side = (i == 0 || first == i - 1) && last == i ? pos + 1 : end - 1;
|
|
3426
|
+
tr.insert(tr.mapping.map(side), nodes);
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
pos = end;
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
return tr.setMeta(fixTablesKey, {
|
|
3433
|
+
fixTables: true
|
|
3434
|
+
});
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
// This file defines a number of table-related commands.
|
|
3438
|
+
// map, table node, and table start offset to the object for
|
|
3439
|
+
// convenience.
|
|
3440
|
+
|
|
3441
|
+
function selectedRect(state) {
|
|
3442
|
+
let sel = state.selection,
|
|
3443
|
+
$pos = selectionCell(state);
|
|
3444
|
+
let table = $pos.node(-1),
|
|
3445
|
+
tableStart = $pos.start(-1),
|
|
3446
|
+
map = TableMap.get(table);
|
|
3447
|
+
let rect;
|
|
3448
|
+
if (sel instanceof CellSelection) rect = map.rectBetween(sel.$anchorCell.pos - tableStart, sel.$headCell.pos - tableStart);else rect = map.findCell($pos.pos - tableStart);
|
|
3449
|
+
rect.tableStart = tableStart;
|
|
3450
|
+
rect.map = map;
|
|
3451
|
+
rect.table = table;
|
|
3452
|
+
return rect;
|
|
3453
|
+
} // Add a column at the given position in a table.
|
|
3454
|
+
|
|
3455
|
+
function addColumn(tr, {
|
|
3456
|
+
map,
|
|
3457
|
+
tableStart,
|
|
3458
|
+
table
|
|
3459
|
+
}, col) {
|
|
3460
|
+
let refColumn = col > 0 ? -1 : 0;
|
|
3461
|
+
if (columnIsHeader(map, table, col + refColumn)) refColumn = col == 0 || col == map.width ? null : 0;
|
|
3462
|
+
|
|
3463
|
+
for (let row = 0; row < map.height; row++) {
|
|
3464
|
+
let index = row * map.width + col; // If this position falls inside a col-spanning cell
|
|
3465
|
+
|
|
3466
|
+
if (col > 0 && col < map.width && map.map[index - 1] == map.map[index]) {
|
|
3467
|
+
let pos = map.map[index],
|
|
3468
|
+
cell = table.nodeAt(pos);
|
|
3469
|
+
tr.setNodeMarkup(tr.mapping.map(tableStart + pos), null, addColSpan(cell.attrs, col - map.colCount(pos))); // Skip ahead if rowspan > 1
|
|
3470
|
+
|
|
3471
|
+
row += cell.attrs.rowspan - 1;
|
|
3472
|
+
} else {
|
|
3473
|
+
let type = refColumn == null ? tableNodeTypes(table.type.schema).cell : table.nodeAt(map.map[index + refColumn]).type;
|
|
3474
|
+
let pos = map.positionAt(row, col, table);
|
|
3475
|
+
tr.insert(tr.mapping.map(tableStart + pos), type.createAndFill());
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
return tr;
|
|
3480
|
+
} // :: (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3481
|
+
// Command to add a column before the column with the selection.
|
|
3482
|
+
|
|
3483
|
+
function addColumnBefore(state, dispatch) {
|
|
3484
|
+
if (!isInTable(state)) return false;
|
|
3485
|
+
|
|
3486
|
+
if (dispatch) {
|
|
3487
|
+
let rect = selectedRect(state);
|
|
3488
|
+
dispatch(addColumn(state.tr, rect, rect.left));
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
return true;
|
|
3492
|
+
} // :: (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3493
|
+
// Command to add a column after the column with the selection.
|
|
3494
|
+
|
|
3495
|
+
function addColumnAfter(state, dispatch) {
|
|
3496
|
+
if (!isInTable(state)) return false;
|
|
3497
|
+
|
|
3498
|
+
if (dispatch) {
|
|
3499
|
+
let rect = selectedRect(state);
|
|
3500
|
+
dispatch(addColumn(state.tr, rect, rect.right));
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
return true;
|
|
3504
|
+
}
|
|
3505
|
+
function removeColumn(tr, {
|
|
3506
|
+
map,
|
|
3507
|
+
table,
|
|
3508
|
+
tableStart
|
|
3509
|
+
}, col) {
|
|
3510
|
+
let mapStart = tr.mapping.maps.length;
|
|
3511
|
+
|
|
3512
|
+
for (let row = 0; row < map.height;) {
|
|
3513
|
+
let index = row * map.width + col,
|
|
3514
|
+
pos = map.map[index],
|
|
3515
|
+
cell = table.nodeAt(pos); // If this is part of a col-spanning cell
|
|
3516
|
+
|
|
3517
|
+
if (col > 0 && map.map[index - 1] == pos || col < map.width - 1 && map.map[index + 1] == pos) {
|
|
3518
|
+
tr.setNodeMarkup(tr.mapping.slice(mapStart).map(tableStart + pos), null, removeColSpan(cell.attrs, col - map.colCount(pos)));
|
|
3519
|
+
} else {
|
|
3520
|
+
let start = tr.mapping.slice(mapStart).map(tableStart + pos);
|
|
3521
|
+
tr.delete(start, start + cell.nodeSize);
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
row += cell.attrs.rowspan;
|
|
3525
|
+
}
|
|
3526
|
+
} // :: (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3527
|
+
// Command function that removes the selected columns from a table.
|
|
3528
|
+
|
|
3529
|
+
function deleteColumn(state, dispatch) {
|
|
3530
|
+
if (!isInTable(state)) return false;
|
|
3531
|
+
|
|
3532
|
+
if (dispatch) {
|
|
3533
|
+
let rect = selectedRect(state),
|
|
3534
|
+
tr = state.tr;
|
|
3535
|
+
if (rect.left == 0 && rect.right == rect.map.width) return false;
|
|
3536
|
+
|
|
3537
|
+
for (let i = rect.right - 1;; i--) {
|
|
3538
|
+
removeColumn(tr, rect, i);
|
|
3539
|
+
if (i == rect.left) break;
|
|
3540
|
+
rect.table = rect.tableStart ? tr.doc.nodeAt(rect.tableStart - 1) : tr.doc;
|
|
3541
|
+
rect.map = TableMap.get(rect.table);
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
dispatch(tr);
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
return true;
|
|
3548
|
+
}
|
|
3549
|
+
function rowIsHeader(map, table, row) {
|
|
3550
|
+
let headerCell = tableNodeTypes(table.type.schema).header_cell;
|
|
3551
|
+
|
|
3552
|
+
for (let col = 0; col < map.width; col++) if (table.nodeAt(map.map[col + row * map.width]).type != headerCell) return false;
|
|
3553
|
+
|
|
3554
|
+
return true;
|
|
3555
|
+
}
|
|
3556
|
+
function addRow(tr, {
|
|
3557
|
+
map,
|
|
3558
|
+
tableStart,
|
|
3559
|
+
table
|
|
3560
|
+
}, row) {
|
|
3561
|
+
let rowPos = tableStart;
|
|
3562
|
+
|
|
3563
|
+
for (let i = 0; i < row; i++) rowPos += table.child(i).nodeSize;
|
|
3564
|
+
|
|
3565
|
+
let cells = [],
|
|
3566
|
+
refRow = row > 0 ? -1 : 0;
|
|
3567
|
+
if (rowIsHeader(map, table, row + refRow)) refRow = row == 0 || row == map.height ? null : 0;
|
|
3568
|
+
|
|
3569
|
+
for (let col = 0, index = map.width * row; col < map.width; col++, index++) {
|
|
3570
|
+
// Covered by a rowspan cell
|
|
3571
|
+
if (row > 0 && row < map.height && map.map[index] == map.map[index - map.width]) {
|
|
3572
|
+
let pos = map.map[index],
|
|
3573
|
+
attrs = table.nodeAt(pos).attrs;
|
|
3574
|
+
tr.setNodeMarkup(tableStart + pos, null, setAttr(attrs, 'rowspan', attrs.rowspan + 1));
|
|
3575
|
+
col += attrs.colspan - 1;
|
|
3576
|
+
} else {
|
|
3577
|
+
let type = refRow == null ? tableNodeTypes(table.type.schema).cell : table.nodeAt(map.map[index + refRow * map.width]).type;
|
|
3578
|
+
cells.push(type.createAndFill());
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
tr.insert(rowPos, tableNodeTypes(table.type.schema).row.create(null, cells));
|
|
3583
|
+
return tr;
|
|
3584
|
+
} // :: (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3585
|
+
// Add a table row before the selection.
|
|
3586
|
+
|
|
3587
|
+
function addRowBefore(state, dispatch) {
|
|
3588
|
+
if (!isInTable(state)) return false;
|
|
3589
|
+
|
|
3590
|
+
if (dispatch) {
|
|
3591
|
+
let rect = selectedRect(state);
|
|
3592
|
+
dispatch(addRow(state.tr, rect, rect.top));
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
return true;
|
|
3596
|
+
} // :: (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3597
|
+
// Add a table row after the selection.
|
|
3598
|
+
|
|
3599
|
+
function addRowAfter(state, dispatch) {
|
|
3600
|
+
if (!isInTable(state)) return false;
|
|
3601
|
+
|
|
3602
|
+
if (dispatch) {
|
|
3603
|
+
let rect = selectedRect(state);
|
|
3604
|
+
dispatch(addRow(state.tr, rect, rect.bottom));
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
return true;
|
|
3608
|
+
}
|
|
3609
|
+
function removeRow(tr, {
|
|
3610
|
+
map,
|
|
3611
|
+
table,
|
|
3612
|
+
tableStart
|
|
3613
|
+
}, row) {
|
|
3614
|
+
let rowPos = 0;
|
|
3615
|
+
|
|
3616
|
+
for (let i = 0; i < row; i++) rowPos += table.child(i).nodeSize;
|
|
3617
|
+
|
|
3618
|
+
let nextRow = rowPos + table.child(row).nodeSize;
|
|
3619
|
+
let mapFrom = tr.mapping.maps.length;
|
|
3620
|
+
tr.delete(rowPos + tableStart, nextRow + tableStart);
|
|
3621
|
+
|
|
3622
|
+
for (let col = 0, index = row * map.width; col < map.width; col++, index++) {
|
|
3623
|
+
let pos = map.map[index];
|
|
3624
|
+
|
|
3625
|
+
if (row > 0 && pos == map.map[index - map.width]) {
|
|
3626
|
+
// If this cell starts in the row above, simply reduce its rowspan
|
|
3627
|
+
let attrs = table.nodeAt(pos).attrs;
|
|
3628
|
+
tr.setNodeMarkup(tr.mapping.slice(mapFrom).map(pos + tableStart), null, setAttr(attrs, 'rowspan', attrs.rowspan - 1));
|
|
3629
|
+
col += attrs.colspan - 1;
|
|
3630
|
+
} else if (row < map.width && pos == map.map[index + map.width]) {
|
|
3631
|
+
// Else, if it continues in the row below, it has to be moved down
|
|
3632
|
+
let cell = table.nodeAt(pos);
|
|
3633
|
+
let copy = cell.type.create(setAttr(cell.attrs, 'rowspan', cell.attrs.rowspan - 1), cell.content);
|
|
3634
|
+
let newPos = map.positionAt(row + 1, col, table);
|
|
3635
|
+
tr.insert(tr.mapping.slice(mapFrom).map(tableStart + newPos), copy);
|
|
3636
|
+
col += cell.attrs.colspan - 1;
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
} // :: (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3640
|
+
// Remove the selected rows from a table.
|
|
3641
|
+
|
|
3642
|
+
function deleteRow(state, dispatch) {
|
|
3643
|
+
if (!isInTable(state)) return false;
|
|
3644
|
+
|
|
3645
|
+
if (dispatch) {
|
|
3646
|
+
let rect = selectedRect(state),
|
|
3647
|
+
tr = state.tr;
|
|
3648
|
+
if (rect.top == 0 && rect.bottom == rect.map.height) return false;
|
|
3649
|
+
|
|
3650
|
+
for (let i = rect.bottom - 1;; i--) {
|
|
3651
|
+
removeRow(tr, rect, i);
|
|
3652
|
+
if (i == rect.top) break;
|
|
3653
|
+
rect.table = rect.tableStart ? tr.doc.nodeAt(rect.tableStart - 1) : tr.doc;
|
|
3654
|
+
rect.map = TableMap.get(rect.table);
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
dispatch(tr);
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
return true;
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
function isEmpty(cell) {
|
|
3664
|
+
let c = cell.content;
|
|
3665
|
+
return c.childCount == 1 && c.firstChild.isTextblock && c.firstChild.childCount == 0;
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3668
|
+
function cellsOverlapRectangle({
|
|
3669
|
+
width,
|
|
3670
|
+
height,
|
|
3671
|
+
map
|
|
3672
|
+
}, rect) {
|
|
3673
|
+
let indexTop = rect.top * width + rect.left,
|
|
3674
|
+
indexLeft = indexTop;
|
|
3675
|
+
let indexBottom = (rect.bottom - 1) * width + rect.left,
|
|
3676
|
+
indexRight = indexTop + (rect.right - rect.left - 1);
|
|
3677
|
+
|
|
3678
|
+
for (let i = rect.top; i < rect.bottom; i++) {
|
|
3679
|
+
if (rect.left > 0 && map[indexLeft] == map[indexLeft - 1] || rect.right < width && map[indexRight] == map[indexRight + 1]) return true;
|
|
3680
|
+
indexLeft += width;
|
|
3681
|
+
indexRight += width;
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
for (let i = rect.left; i < rect.right; i++) {
|
|
3685
|
+
if (rect.top > 0 && map[indexTop] == map[indexTop - width] || rect.bottom < height && map[indexBottom] == map[indexBottom + width]) return true;
|
|
3686
|
+
indexTop++;
|
|
3687
|
+
indexBottom++;
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
return false;
|
|
3691
|
+
} // :: (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3692
|
+
// Merge the selected cells into a single cell. Only available when
|
|
3693
|
+
// the selected cells' outline forms a rectangle.
|
|
3694
|
+
|
|
3695
|
+
|
|
3696
|
+
function mergeCells(state, dispatch) {
|
|
3697
|
+
let sel = state.selection;
|
|
3698
|
+
if (!(sel instanceof CellSelection) || sel.$anchorCell.pos == sel.$headCell.pos) return false;
|
|
3699
|
+
let rect = selectedRect(state),
|
|
3700
|
+
{
|
|
3701
|
+
map
|
|
3702
|
+
} = rect;
|
|
3703
|
+
if (cellsOverlapRectangle(map, rect)) return false;
|
|
3704
|
+
|
|
3705
|
+
if (dispatch) {
|
|
3706
|
+
let tr = state.tr,
|
|
3707
|
+
seen = {},
|
|
3708
|
+
content = prosemirrorModel.Fragment.empty,
|
|
3709
|
+
mergedPos,
|
|
3710
|
+
mergedCell;
|
|
3711
|
+
|
|
3712
|
+
for (let row = rect.top; row < rect.bottom; row++) {
|
|
3713
|
+
for (let col = rect.left; col < rect.right; col++) {
|
|
3714
|
+
let cellPos = map.map[row * map.width + col],
|
|
3715
|
+
cell = rect.table.nodeAt(cellPos);
|
|
3716
|
+
if (seen[cellPos]) continue;
|
|
3717
|
+
seen[cellPos] = true;
|
|
3718
|
+
|
|
3719
|
+
if (mergedPos == null) {
|
|
3720
|
+
mergedPos = cellPos;
|
|
3721
|
+
mergedCell = cell;
|
|
3722
|
+
} else {
|
|
3723
|
+
if (!isEmpty(cell)) content = content.append(cell.content);
|
|
3724
|
+
let mapped = tr.mapping.map(cellPos + rect.tableStart);
|
|
3725
|
+
tr.delete(mapped, mapped + cell.nodeSize);
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
tr.setNodeMarkup(mergedPos + rect.tableStart, null, setAttr(addColSpan(mergedCell.attrs, mergedCell.attrs.colspan, rect.right - rect.left - mergedCell.attrs.colspan), 'rowspan', rect.bottom - rect.top));
|
|
3731
|
+
|
|
3732
|
+
if (content.size) {
|
|
3733
|
+
let end = mergedPos + 1 + mergedCell.content.size;
|
|
3734
|
+
let start = isEmpty(mergedCell) ? mergedPos + 1 : end;
|
|
3735
|
+
tr.replaceWith(start + rect.tableStart, end + rect.tableStart, content);
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
tr.setSelection(new CellSelection(tr.doc.resolve(mergedPos + rect.tableStart)));
|
|
3739
|
+
dispatch(tr);
|
|
3740
|
+
}
|
|
3741
|
+
|
|
3742
|
+
return true;
|
|
3743
|
+
} // :: (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3744
|
+
// Split a selected cell, whose rowpan or colspan is greater than one,
|
|
3745
|
+
// into smaller cells. Use the first cell type for the new cells.
|
|
3746
|
+
|
|
3747
|
+
function splitCell(state, dispatch) {
|
|
3748
|
+
const nodeTypes = tableNodeTypes(state.schema);
|
|
3749
|
+
return splitCellWithType(({
|
|
3750
|
+
node
|
|
3751
|
+
}) => {
|
|
3752
|
+
return nodeTypes[node.type.spec.tableRole];
|
|
3753
|
+
})(state, dispatch);
|
|
3754
|
+
} // :: (getCellType: ({ row: number, col: number, node: Node}) → NodeType) → (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3755
|
+
// Split a selected cell, whose rowpan or colspan is greater than one,
|
|
3756
|
+
// into smaller cells with the cell type (th, td) returned by getType function.
|
|
3757
|
+
|
|
3758
|
+
function splitCellWithType(getCellType) {
|
|
3759
|
+
return (state, dispatch) => {
|
|
3760
|
+
let sel = state.selection;
|
|
3761
|
+
let cellNode, cellPos;
|
|
3762
|
+
|
|
3763
|
+
if (!(sel instanceof CellSelection)) {
|
|
3764
|
+
cellNode = cellWrapping(sel.$from);
|
|
3765
|
+
if (!cellNode) return false;
|
|
3766
|
+
cellPos = cellAround(sel.$from).pos;
|
|
3767
|
+
} else {
|
|
3768
|
+
if (sel.$anchorCell.pos != sel.$headCell.pos) return false;
|
|
3769
|
+
cellNode = sel.$anchorCell.nodeAfter;
|
|
3770
|
+
cellPos = sel.$anchorCell.pos;
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
if (cellNode.attrs.colspan == 1 && cellNode.attrs.rowspan == 1) {
|
|
3774
|
+
return false;
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
if (dispatch) {
|
|
3778
|
+
let baseAttrs = cellNode.attrs,
|
|
3779
|
+
attrs = [],
|
|
3780
|
+
colwidth = baseAttrs.colwidth;
|
|
3781
|
+
if (baseAttrs.rowspan > 1) baseAttrs = setAttr(baseAttrs, 'rowspan', 1);
|
|
3782
|
+
if (baseAttrs.colspan > 1) baseAttrs = setAttr(baseAttrs, 'colspan', 1);
|
|
3783
|
+
let rect = selectedRect(state),
|
|
3784
|
+
tr = state.tr;
|
|
3785
|
+
|
|
3786
|
+
for (let i = 0; i < rect.right - rect.left; i++) attrs.push(colwidth ? setAttr(baseAttrs, 'colwidth', colwidth && colwidth[i] ? [colwidth[i]] : null) : baseAttrs);
|
|
3787
|
+
|
|
3788
|
+
let lastCell;
|
|
3789
|
+
|
|
3790
|
+
for (let row = rect.top; row < rect.bottom; row++) {
|
|
3791
|
+
let pos = rect.map.positionAt(row, rect.left, rect.table);
|
|
3792
|
+
if (row == rect.top) pos += cellNode.nodeSize;
|
|
3793
|
+
|
|
3794
|
+
for (let col = rect.left, i = 0; col < rect.right; col++, i++) {
|
|
3795
|
+
if (col == rect.left && row == rect.top) continue;
|
|
3796
|
+
tr.insert(lastCell = tr.mapping.map(pos + rect.tableStart, 1), getCellType({
|
|
3797
|
+
node: cellNode,
|
|
3798
|
+
row,
|
|
3799
|
+
col
|
|
3800
|
+
}).createAndFill(attrs[i]));
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3804
|
+
tr.setNodeMarkup(cellPos, getCellType({
|
|
3805
|
+
node: cellNode,
|
|
3806
|
+
row: rect.top,
|
|
3807
|
+
col: rect.left
|
|
3808
|
+
}), attrs[0]);
|
|
3809
|
+
if (sel instanceof CellSelection) tr.setSelection(new CellSelection(tr.doc.resolve(sel.$anchorCell.pos), lastCell && tr.doc.resolve(lastCell)));
|
|
3810
|
+
dispatch(tr);
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
return true;
|
|
3814
|
+
};
|
|
3815
|
+
} // :: (string, any) → (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3816
|
+
// Returns a command that sets the given attribute to the given value,
|
|
3817
|
+
// and is only available when the currently selected cell doesn't
|
|
3818
|
+
// already have that attribute set to that value.
|
|
3819
|
+
|
|
3820
|
+
function setCellAttr(name, value) {
|
|
3821
|
+
return function (state, dispatch) {
|
|
3822
|
+
if (!isInTable(state)) return false;
|
|
3823
|
+
let $cell = selectionCell(state);
|
|
3824
|
+
if ($cell.nodeAfter.attrs[name] === value) return false;
|
|
3825
|
+
|
|
3826
|
+
if (dispatch) {
|
|
3827
|
+
let tr = state.tr;
|
|
3828
|
+
if (state.selection instanceof CellSelection) state.selection.forEachCell((node, pos) => {
|
|
3829
|
+
if (node.attrs[name] !== value) tr.setNodeMarkup(pos, null, setAttr(node.attrs, name, value));
|
|
3830
|
+
});else tr.setNodeMarkup($cell.pos, null, setAttr($cell.nodeAfter.attrs, name, value));
|
|
3831
|
+
dispatch(tr);
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
return true;
|
|
3835
|
+
};
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
function deprecated_toggleHeader(type) {
|
|
3839
|
+
return function (state, dispatch) {
|
|
3840
|
+
if (!isInTable(state)) return false;
|
|
3841
|
+
|
|
3842
|
+
if (dispatch) {
|
|
3843
|
+
let types = tableNodeTypes(state.schema);
|
|
3844
|
+
let rect = selectedRect(state),
|
|
3845
|
+
tr = state.tr;
|
|
3846
|
+
let cells = rect.map.cellsInRect(type == 'column' ? new Rect(rect.left, 0, rect.right, rect.map.height) : type == 'row' ? new Rect(0, rect.top, rect.map.width, rect.bottom) : rect);
|
|
3847
|
+
let nodes = cells.map(pos => rect.table.nodeAt(pos));
|
|
3848
|
+
|
|
3849
|
+
for (let i = 0; i < cells.length; i++ // Remove headers, if any
|
|
3850
|
+
) if (nodes[i].type == types.header_cell) tr.setNodeMarkup(rect.tableStart + cells[i], types.cell, nodes[i].attrs);
|
|
3851
|
+
|
|
3852
|
+
if (tr.steps.length == 0) for (let i = 0; i < cells.length; i++ // No headers removed, add instead
|
|
3853
|
+
) tr.setNodeMarkup(rect.tableStart + cells[i], types.header_cell, nodes[i].attrs);
|
|
3854
|
+
dispatch(tr);
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3857
|
+
return true;
|
|
3858
|
+
};
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
function isHeaderEnabledByType(type, rect, types) {
|
|
3862
|
+
// Get cell positions for first row or first column
|
|
3863
|
+
const cellPositions = rect.map.cellsInRect({
|
|
3864
|
+
left: 0,
|
|
3865
|
+
top: 0,
|
|
3866
|
+
right: type == 'row' ? rect.map.width : 1,
|
|
3867
|
+
bottom: type == 'column' ? rect.map.height : 1
|
|
3868
|
+
});
|
|
3869
|
+
|
|
3870
|
+
for (let i = 0; i < cellPositions.length; i++) {
|
|
3871
|
+
const cell = rect.table.nodeAt(cellPositions[i]);
|
|
3872
|
+
|
|
3873
|
+
if (cell && cell.type !== types.header_cell) {
|
|
3874
|
+
return false;
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
return true;
|
|
3879
|
+
} // :: (string, ?{ useDeprecatedLogic: bool }) → (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3880
|
+
// Toggles between row/column header and normal cells (Only applies to first row/column).
|
|
3881
|
+
// For deprecated behavior pass `useDeprecatedLogic` in options with true.
|
|
3882
|
+
|
|
3883
|
+
|
|
3884
|
+
function toggleHeader(type, options) {
|
|
3885
|
+
options = options || {
|
|
3886
|
+
useDeprecatedLogic: false
|
|
3887
|
+
};
|
|
3888
|
+
if (options.useDeprecatedLogic) return deprecated_toggleHeader(type);
|
|
3889
|
+
return function (state, dispatch) {
|
|
3890
|
+
if (!isInTable(state)) return false;
|
|
3891
|
+
|
|
3892
|
+
if (dispatch) {
|
|
3893
|
+
let types = tableNodeTypes(state.schema);
|
|
3894
|
+
let rect = selectedRect(state),
|
|
3895
|
+
tr = state.tr;
|
|
3896
|
+
let isHeaderRowEnabled = isHeaderEnabledByType('row', rect, types);
|
|
3897
|
+
let isHeaderColumnEnabled = isHeaderEnabledByType('column', rect, types);
|
|
3898
|
+
let isHeaderEnabled = type === 'column' ? isHeaderRowEnabled : type === 'row' ? isHeaderColumnEnabled : false;
|
|
3899
|
+
let selectionStartsAt = isHeaderEnabled ? 1 : 0;
|
|
3900
|
+
let cellsRect = type == 'column' ? new Rect(0, selectionStartsAt, 1, rect.map.height) : type == 'row' ? new Rect(selectionStartsAt, 0, rect.map.width, 1) : rect;
|
|
3901
|
+
let newType = type == 'column' ? isHeaderColumnEnabled ? types.cell : types.header_cell : type == 'row' ? isHeaderRowEnabled ? types.cell : types.header_cell : types.cell;
|
|
3902
|
+
rect.map.cellsInRect(cellsRect).forEach(relativeCellPos => {
|
|
3903
|
+
const cellPos = relativeCellPos + rect.tableStart;
|
|
3904
|
+
const cell = tr.doc.nodeAt(cellPos);
|
|
3905
|
+
|
|
3906
|
+
if (cell) {
|
|
3907
|
+
tr.setNodeMarkup(cellPos, newType, cell.attrs);
|
|
3908
|
+
}
|
|
3909
|
+
});
|
|
3910
|
+
dispatch(tr);
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
return true;
|
|
3914
|
+
};
|
|
3915
|
+
} // :: (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3916
|
+
// Toggles whether the selected row contains header cells.
|
|
3917
|
+
|
|
3918
|
+
toggleHeader('row', {
|
|
3919
|
+
useDeprecatedLogic: true
|
|
3920
|
+
}); // :: (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3921
|
+
// Toggles whether the selected column contains header cells.
|
|
3922
|
+
|
|
3923
|
+
toggleHeader('column', {
|
|
3924
|
+
useDeprecatedLogic: true
|
|
3925
|
+
}); // :: (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3926
|
+
// Toggles whether the selected cells are header cells.
|
|
3927
|
+
|
|
3928
|
+
let toggleHeaderCell = toggleHeader('cell', {
|
|
3929
|
+
useDeprecatedLogic: true
|
|
3930
|
+
});
|
|
3931
|
+
|
|
3932
|
+
function findNextCell($cell, dir) {
|
|
3933
|
+
if (dir < 0) {
|
|
3934
|
+
let before = $cell.nodeBefore;
|
|
3935
|
+
if (before) return $cell.pos - before.nodeSize;
|
|
3936
|
+
|
|
3937
|
+
for (let row = $cell.index(-1) - 1, rowEnd = $cell.before(); row >= 0; row--) {
|
|
3938
|
+
let rowNode = $cell.node(-1).child(row);
|
|
3939
|
+
if (rowNode.childCount) return rowEnd - 1 - rowNode.lastChild.nodeSize;
|
|
3940
|
+
rowEnd -= rowNode.nodeSize;
|
|
3941
|
+
}
|
|
3942
|
+
} else {
|
|
3943
|
+
if ($cell.index() < $cell.parent.childCount - 1) return $cell.pos + $cell.nodeAfter.nodeSize;
|
|
3944
|
+
let table = $cell.node(-1);
|
|
3945
|
+
|
|
3946
|
+
for (let row = $cell.indexAfter(-1), rowStart = $cell.after(); row < table.childCount; row++) {
|
|
3947
|
+
let rowNode = table.child(row);
|
|
3948
|
+
if (rowNode.childCount) return rowStart + 1;
|
|
3949
|
+
rowStart += rowNode.nodeSize;
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
} // :: (number) → (EditorState, dispatch: ?(tr: Transaction)) → bool
|
|
3953
|
+
// Returns a command for selecting the next (direction=1) or previous
|
|
3954
|
+
// (direction=-1) cell in a table.
|
|
3955
|
+
|
|
3956
|
+
|
|
3957
|
+
function goToNextCell(direction) {
|
|
3958
|
+
return function (state, dispatch) {
|
|
3959
|
+
if (!isInTable(state)) return false;
|
|
3960
|
+
let cell = findNextCell(selectionCell(state), direction);
|
|
3961
|
+
if (cell == null) return;
|
|
3962
|
+
|
|
3963
|
+
if (dispatch) {
|
|
3964
|
+
let $cell = state.doc.resolve(cell);
|
|
3965
|
+
dispatch(state.tr.setSelection(prosemirrorState.TextSelection.between($cell, moveCellForward($cell))).scrollIntoView());
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3968
|
+
return true;
|
|
3969
|
+
};
|
|
3970
|
+
} // :: (EditorState, ?(tr: Transaction)) → bool
|
|
3971
|
+
// Deletes the table around the selection, if any.
|
|
3972
|
+
|
|
3973
|
+
function deleteTable(state, dispatch) {
|
|
3974
|
+
let $pos = state.selection.$anchor;
|
|
3975
|
+
|
|
3976
|
+
for (let d = $pos.depth; d > 0; d--) {
|
|
3977
|
+
let node = $pos.node(d);
|
|
3978
|
+
|
|
3979
|
+
if (node.type.spec.tableRole == 'table') {
|
|
3980
|
+
if (dispatch) dispatch(state.tr.delete($pos.before(d), $pos.after(d)).scrollIntoView());
|
|
3981
|
+
return true;
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
return false;
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
class TableView$1 {
|
|
3989
|
+
constructor(node, cellMinWidth) {
|
|
3990
|
+
this.node = node;
|
|
3991
|
+
this.cellMinWidth = cellMinWidth;
|
|
3992
|
+
this.dom = document.createElement('div');
|
|
3993
|
+
this.dom.className = 'tableWrapper';
|
|
3994
|
+
this.table = this.dom.appendChild(document.createElement('table'));
|
|
3995
|
+
this.colgroup = this.table.appendChild(document.createElement('colgroup'));
|
|
3996
|
+
updateColumns$1(node, this.colgroup, this.table, cellMinWidth);
|
|
3997
|
+
this.contentDOM = this.table.appendChild(document.createElement('tbody'));
|
|
3998
|
+
}
|
|
3999
|
+
|
|
4000
|
+
update(node) {
|
|
4001
|
+
if (node.type != this.node.type) return false;
|
|
4002
|
+
this.node = node;
|
|
4003
|
+
updateColumns$1(node, this.colgroup, this.table, this.cellMinWidth);
|
|
4004
|
+
return true;
|
|
4005
|
+
}
|
|
4006
|
+
|
|
4007
|
+
ignoreMutation(record) {
|
|
4008
|
+
return record.type == 'attributes' && (record.target == this.table || this.colgroup.contains(record.target));
|
|
4009
|
+
}
|
|
4010
|
+
|
|
4011
|
+
}
|
|
4012
|
+
function updateColumns$1(node, colgroup, table, cellMinWidth, overrideCol, overrideValue) {
|
|
4013
|
+
let totalWidth = 0,
|
|
4014
|
+
fixedWidth = true;
|
|
4015
|
+
let nextDOM = colgroup.firstChild,
|
|
4016
|
+
row = node.firstChild;
|
|
4017
|
+
|
|
4018
|
+
for (let i = 0, col = 0; i < row.childCount; i++) {
|
|
4019
|
+
let {
|
|
4020
|
+
colspan,
|
|
4021
|
+
colwidth
|
|
4022
|
+
} = row.child(i).attrs;
|
|
4023
|
+
|
|
4024
|
+
for (let j = 0; j < colspan; j++, col++) {
|
|
4025
|
+
let hasWidth = overrideCol == col ? overrideValue : colwidth && colwidth[j];
|
|
4026
|
+
let cssWidth = hasWidth ? hasWidth + 'px' : '';
|
|
4027
|
+
totalWidth += hasWidth || cellMinWidth;
|
|
4028
|
+
if (!hasWidth) fixedWidth = false;
|
|
4029
|
+
|
|
4030
|
+
if (!nextDOM) {
|
|
4031
|
+
colgroup.appendChild(document.createElement('col')).style.width = cssWidth;
|
|
4032
|
+
} else {
|
|
4033
|
+
if (nextDOM.style.width != cssWidth) nextDOM.style.width = cssWidth;
|
|
4034
|
+
nextDOM = nextDOM.nextSibling;
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
while (nextDOM) {
|
|
4040
|
+
let after = nextDOM.nextSibling;
|
|
4041
|
+
nextDOM.parentNode.removeChild(nextDOM);
|
|
4042
|
+
nextDOM = after;
|
|
4043
|
+
}
|
|
4044
|
+
|
|
4045
|
+
if (fixedWidth) {
|
|
4046
|
+
table.style.width = totalWidth + 'px';
|
|
4047
|
+
table.style.minWidth = '';
|
|
4048
|
+
} else {
|
|
4049
|
+
table.style.width = '';
|
|
4050
|
+
table.style.minWidth = totalWidth + 'px';
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
|
|
4054
|
+
const key = new prosemirrorState.PluginKey('tableColumnResizing');
|
|
4055
|
+
function columnResizing({
|
|
4056
|
+
handleWidth = 5,
|
|
4057
|
+
cellMinWidth = 25,
|
|
4058
|
+
View = TableView$1,
|
|
4059
|
+
lastColumnResizable = true
|
|
4060
|
+
} = {}) {
|
|
4061
|
+
let plugin = new prosemirrorState.Plugin({
|
|
4062
|
+
key,
|
|
4063
|
+
state: {
|
|
4064
|
+
init(_, state) {
|
|
4065
|
+
this.spec.props.nodeViews[tableNodeTypes(state.schema).table.name] = (node, view) => new View(node, cellMinWidth, view);
|
|
4066
|
+
|
|
4067
|
+
return new ResizeState(-1, false);
|
|
4068
|
+
},
|
|
4069
|
+
|
|
4070
|
+
apply(tr, prev) {
|
|
4071
|
+
return prev.apply(tr);
|
|
4072
|
+
}
|
|
4073
|
+
|
|
4074
|
+
},
|
|
4075
|
+
props: {
|
|
4076
|
+
attributes(state) {
|
|
4077
|
+
let pluginState = key.getState(state);
|
|
4078
|
+
return pluginState.activeHandle > -1 ? {
|
|
4079
|
+
class: 'resize-cursor'
|
|
4080
|
+
} : null;
|
|
4081
|
+
},
|
|
4082
|
+
|
|
4083
|
+
handleDOMEvents: {
|
|
4084
|
+
mousemove(view, event) {
|
|
4085
|
+
handleMouseMove(view, event, handleWidth, cellMinWidth, lastColumnResizable);
|
|
4086
|
+
},
|
|
4087
|
+
|
|
4088
|
+
mouseleave(view) {
|
|
4089
|
+
handleMouseLeave(view);
|
|
4090
|
+
},
|
|
4091
|
+
|
|
4092
|
+
mousedown(view, event) {
|
|
4093
|
+
handleMouseDown(view, event, cellMinWidth);
|
|
4094
|
+
}
|
|
4095
|
+
|
|
4096
|
+
},
|
|
4097
|
+
|
|
4098
|
+
decorations(state) {
|
|
4099
|
+
let pluginState = key.getState(state);
|
|
4100
|
+
if (pluginState.activeHandle > -1) return handleDecorations(state, pluginState.activeHandle);
|
|
4101
|
+
},
|
|
4102
|
+
|
|
4103
|
+
nodeViews: {}
|
|
4104
|
+
}
|
|
4105
|
+
});
|
|
4106
|
+
return plugin;
|
|
4107
|
+
}
|
|
4108
|
+
|
|
4109
|
+
class ResizeState {
|
|
4110
|
+
constructor(activeHandle, dragging) {
|
|
4111
|
+
this.activeHandle = activeHandle;
|
|
4112
|
+
this.dragging = dragging;
|
|
4113
|
+
}
|
|
4114
|
+
|
|
4115
|
+
apply(tr) {
|
|
4116
|
+
let state = this,
|
|
4117
|
+
action = tr.getMeta(key);
|
|
4118
|
+
if (action && action.setHandle != null) return new ResizeState(action.setHandle, null);
|
|
4119
|
+
if (action && action.setDragging !== undefined) return new ResizeState(state.activeHandle, action.setDragging);
|
|
4120
|
+
|
|
4121
|
+
if (state.activeHandle > -1 && tr.docChanged) {
|
|
4122
|
+
let handle = tr.mapping.map(state.activeHandle, -1);
|
|
4123
|
+
if (!pointsAtCell(tr.doc.resolve(handle))) handle = null;
|
|
4124
|
+
state = new ResizeState(handle, state.dragging);
|
|
4125
|
+
}
|
|
4126
|
+
|
|
4127
|
+
return state;
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
}
|
|
4131
|
+
|
|
4132
|
+
function handleMouseMove(view, event, handleWidth, cellMinWidth, lastColumnResizable) {
|
|
4133
|
+
let pluginState = key.getState(view.state);
|
|
4134
|
+
|
|
4135
|
+
if (!pluginState.dragging) {
|
|
4136
|
+
let target = domCellAround(event.target),
|
|
4137
|
+
cell = -1;
|
|
4138
|
+
|
|
4139
|
+
if (target) {
|
|
4140
|
+
let {
|
|
4141
|
+
left,
|
|
4142
|
+
right
|
|
4143
|
+
} = target.getBoundingClientRect();
|
|
4144
|
+
if (event.clientX - left <= handleWidth) cell = edgeCell(view, event, 'left');else if (right - event.clientX <= handleWidth) cell = edgeCell(view, event, 'right');
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
if (cell != pluginState.activeHandle) {
|
|
4148
|
+
if (!lastColumnResizable && cell !== -1) {
|
|
4149
|
+
let $cell = view.state.doc.resolve(cell);
|
|
4150
|
+
let table = $cell.node(-1),
|
|
4151
|
+
map = TableMap.get(table),
|
|
4152
|
+
start = $cell.start(-1);
|
|
4153
|
+
let col = map.colCount($cell.pos - start) + $cell.nodeAfter.attrs.colspan - 1;
|
|
4154
|
+
|
|
4155
|
+
if (col == map.width - 1) {
|
|
4156
|
+
return;
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
updateHandle(view, cell);
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
function handleMouseLeave(view) {
|
|
4166
|
+
let pluginState = key.getState(view.state);
|
|
4167
|
+
if (pluginState.activeHandle > -1 && !pluginState.dragging) updateHandle(view, -1);
|
|
4168
|
+
}
|
|
4169
|
+
|
|
4170
|
+
function handleMouseDown(view, event, cellMinWidth) {
|
|
4171
|
+
let pluginState = key.getState(view.state);
|
|
4172
|
+
if (pluginState.activeHandle == -1 || pluginState.dragging) return false;
|
|
4173
|
+
let cell = view.state.doc.nodeAt(pluginState.activeHandle);
|
|
4174
|
+
let width = currentColWidth(view, pluginState.activeHandle, cell.attrs);
|
|
4175
|
+
view.dispatch(view.state.tr.setMeta(key, {
|
|
4176
|
+
setDragging: {
|
|
4177
|
+
startX: event.clientX,
|
|
4178
|
+
startWidth: width
|
|
4179
|
+
}
|
|
4180
|
+
}));
|
|
4181
|
+
|
|
4182
|
+
function finish(event) {
|
|
4183
|
+
window.removeEventListener('mouseup', finish);
|
|
4184
|
+
window.removeEventListener('mousemove', move);
|
|
4185
|
+
let pluginState = key.getState(view.state);
|
|
4186
|
+
|
|
4187
|
+
if (pluginState.dragging) {
|
|
4188
|
+
updateColumnWidth(view, pluginState.activeHandle, draggedWidth(pluginState.dragging, event, cellMinWidth));
|
|
4189
|
+
view.dispatch(view.state.tr.setMeta(key, {
|
|
4190
|
+
setDragging: null
|
|
4191
|
+
}));
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
function move(event) {
|
|
4196
|
+
if (!event.which) return finish(event);
|
|
4197
|
+
let pluginState = key.getState(view.state);
|
|
4198
|
+
let dragged = draggedWidth(pluginState.dragging, event, cellMinWidth);
|
|
4199
|
+
displayColumnWidth(view, pluginState.activeHandle, dragged, cellMinWidth);
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
window.addEventListener('mouseup', finish);
|
|
4203
|
+
window.addEventListener('mousemove', move);
|
|
4204
|
+
event.preventDefault();
|
|
4205
|
+
return true;
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
function currentColWidth(view, cellPos, {
|
|
4209
|
+
colspan,
|
|
4210
|
+
colwidth
|
|
4211
|
+
}) {
|
|
4212
|
+
let width = colwidth && colwidth[colwidth.length - 1];
|
|
4213
|
+
if (width) return width;
|
|
4214
|
+
let dom = view.domAtPos(cellPos);
|
|
4215
|
+
let node = dom.node.childNodes[dom.offset];
|
|
4216
|
+
let domWidth = node.offsetWidth,
|
|
4217
|
+
parts = colspan;
|
|
4218
|
+
if (colwidth) for (let i = 0; i < colspan; i++) if (colwidth[i]) {
|
|
4219
|
+
domWidth -= colwidth[i];
|
|
4220
|
+
parts--;
|
|
4221
|
+
}
|
|
4222
|
+
return domWidth / parts;
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
function domCellAround(target) {
|
|
4226
|
+
while (target && target.nodeName != 'TD' && target.nodeName != 'TH') target = target.classList.contains('ProseMirror') ? null : target.parentNode;
|
|
4227
|
+
|
|
4228
|
+
return target;
|
|
4229
|
+
}
|
|
4230
|
+
|
|
4231
|
+
function edgeCell(view, event, side) {
|
|
4232
|
+
let found = view.posAtCoords({
|
|
4233
|
+
left: event.clientX,
|
|
4234
|
+
top: event.clientY
|
|
4235
|
+
});
|
|
4236
|
+
if (!found) return -1;
|
|
4237
|
+
let {
|
|
4238
|
+
pos
|
|
4239
|
+
} = found;
|
|
4240
|
+
let $cell = cellAround(view.state.doc.resolve(pos));
|
|
4241
|
+
if (!$cell) return -1;
|
|
4242
|
+
if (side == 'right') return $cell.pos;
|
|
4243
|
+
let map = TableMap.get($cell.node(-1)),
|
|
4244
|
+
start = $cell.start(-1);
|
|
4245
|
+
let index = map.map.indexOf($cell.pos - start);
|
|
4246
|
+
return index % map.width == 0 ? -1 : start + map.map[index - 1];
|
|
4247
|
+
}
|
|
4248
|
+
|
|
4249
|
+
function draggedWidth(dragging, event, cellMinWidth) {
|
|
4250
|
+
let offset = event.clientX - dragging.startX;
|
|
4251
|
+
return Math.max(cellMinWidth, dragging.startWidth + offset);
|
|
4252
|
+
}
|
|
4253
|
+
|
|
4254
|
+
function updateHandle(view, value) {
|
|
4255
|
+
view.dispatch(view.state.tr.setMeta(key, {
|
|
4256
|
+
setHandle: value
|
|
4257
|
+
}));
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4260
|
+
function updateColumnWidth(view, cell, width) {
|
|
4261
|
+
let $cell = view.state.doc.resolve(cell);
|
|
4262
|
+
let table = $cell.node(-1),
|
|
4263
|
+
map = TableMap.get(table),
|
|
4264
|
+
start = $cell.start(-1);
|
|
4265
|
+
let col = map.colCount($cell.pos - start) + $cell.nodeAfter.attrs.colspan - 1;
|
|
4266
|
+
let tr = view.state.tr;
|
|
4267
|
+
|
|
4268
|
+
for (let row = 0; row < map.height; row++) {
|
|
4269
|
+
let mapIndex = row * map.width + col; // Rowspanning cell that has already been handled
|
|
4270
|
+
|
|
4271
|
+
if (row && map.map[mapIndex] == map.map[mapIndex - map.width]) continue;
|
|
4272
|
+
let pos = map.map[mapIndex],
|
|
4273
|
+
{
|
|
4274
|
+
attrs
|
|
4275
|
+
} = table.nodeAt(pos);
|
|
4276
|
+
let index = attrs.colspan == 1 ? 0 : col - map.colCount(pos);
|
|
4277
|
+
if (attrs.colwidth && attrs.colwidth[index] == width) continue;
|
|
4278
|
+
let colwidth = attrs.colwidth ? attrs.colwidth.slice() : zeroes(attrs.colspan);
|
|
4279
|
+
colwidth[index] = width;
|
|
4280
|
+
tr.setNodeMarkup(start + pos, null, setAttr(attrs, 'colwidth', colwidth));
|
|
4281
|
+
}
|
|
4282
|
+
|
|
4283
|
+
if (tr.docChanged) view.dispatch(tr);
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
function displayColumnWidth(view, cell, width, cellMinWidth) {
|
|
4287
|
+
let $cell = view.state.doc.resolve(cell);
|
|
4288
|
+
let table = $cell.node(-1),
|
|
4289
|
+
start = $cell.start(-1);
|
|
4290
|
+
let col = TableMap.get(table).colCount($cell.pos - start) + $cell.nodeAfter.attrs.colspan - 1;
|
|
4291
|
+
let dom = view.domAtPos($cell.start(-1)).node;
|
|
4292
|
+
|
|
4293
|
+
while (dom.nodeName != 'TABLE') dom = dom.parentNode;
|
|
4294
|
+
|
|
4295
|
+
updateColumns$1(table, dom.firstChild, dom, cellMinWidth, col, width);
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4298
|
+
function zeroes(n) {
|
|
4299
|
+
let result = [];
|
|
4300
|
+
|
|
4301
|
+
for (let i = 0; i < n; i++) result.push(0);
|
|
4302
|
+
|
|
4303
|
+
return result;
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4306
|
+
function handleDecorations(state, cell) {
|
|
4307
|
+
let decorations = [];
|
|
4308
|
+
let $cell = state.doc.resolve(cell);
|
|
4309
|
+
let table = $cell.node(-1),
|
|
4310
|
+
map = TableMap.get(table),
|
|
4311
|
+
start = $cell.start(-1);
|
|
4312
|
+
let col = map.colCount($cell.pos - start) + $cell.nodeAfter.attrs.colspan;
|
|
4313
|
+
|
|
4314
|
+
for (let row = 0; row < map.height; row++) {
|
|
4315
|
+
let index = col + row * map.width - 1; // For positions that are have either a different cell or the end
|
|
4316
|
+
// of the table to their right, and either the top of the table or
|
|
4317
|
+
// a different cell above them, add a decoration
|
|
4318
|
+
|
|
4319
|
+
if ((col == map.width || map.map[index] != map.map[index + 1]) && (row == 0 || map.map[index - 1] != map.map[index - 1 - map.width])) {
|
|
4320
|
+
let cellPos = map.map[index];
|
|
4321
|
+
let pos = start + cellPos + table.nodeAt(cellPos).nodeSize - 1;
|
|
4322
|
+
let dom = document.createElement('div');
|
|
4323
|
+
dom.className = 'column-resize-handle';
|
|
4324
|
+
decorations.push(prosemirrorView.Decoration.widget(pos, dom));
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
return prosemirrorView.DecorationSet.create(state.doc, decorations);
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
// This file defines a plugin that handles the drawing of cell
|
|
4332
|
+
//
|
|
4333
|
+
// Creates a [plugin](http://prosemirror.net/docs/ref/#state.Plugin)
|
|
4334
|
+
// that, when added to an editor, enables cell-selection, handles
|
|
4335
|
+
// cell-based copy/paste, and makes sure tables stay well-formed (each
|
|
4336
|
+
// row has the same width, and cells don't overlap).
|
|
4337
|
+
//
|
|
4338
|
+
// You should probably put this plugin near the end of your array of
|
|
4339
|
+
// plugins, since it handles mouse and arrow key events in tables
|
|
4340
|
+
// rather broadly, and other plugins, like the gap cursor or the
|
|
4341
|
+
// column-width dragging plugin, might want to get a turn first to
|
|
4342
|
+
// perform more specific behavior.
|
|
4343
|
+
|
|
4344
|
+
function tableEditing({
|
|
4345
|
+
allowTableNodeSelection = false
|
|
4346
|
+
} = {}) {
|
|
4347
|
+
return new prosemirrorState.Plugin({
|
|
4348
|
+
key: key$1,
|
|
4349
|
+
// This piece of state is used to remember when a mouse-drag
|
|
4350
|
+
// cell-selection is happening, so that it can continue even as
|
|
4351
|
+
// transactions (which might move its anchor cell) come in.
|
|
4352
|
+
state: {
|
|
4353
|
+
init() {
|
|
4354
|
+
return null;
|
|
4355
|
+
},
|
|
4356
|
+
|
|
4357
|
+
apply(tr, cur) {
|
|
4358
|
+
let set = tr.getMeta(key$1);
|
|
4359
|
+
if (set != null) return set == -1 ? null : set;
|
|
4360
|
+
if (cur == null || !tr.docChanged) return cur;
|
|
4361
|
+
let {
|
|
4362
|
+
deleted,
|
|
4363
|
+
pos
|
|
4364
|
+
} = tr.mapping.mapResult(cur);
|
|
4365
|
+
return deleted ? null : pos;
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
},
|
|
4369
|
+
props: {
|
|
4370
|
+
decorations: drawCellSelection,
|
|
4371
|
+
handleDOMEvents: {
|
|
4372
|
+
mousedown: handleMouseDown$1
|
|
4373
|
+
},
|
|
4374
|
+
|
|
4375
|
+
createSelectionBetween(view) {
|
|
4376
|
+
if (key$1.getState(view.state) != null) return view.state.selection;
|
|
4377
|
+
},
|
|
4378
|
+
|
|
4379
|
+
handleTripleClick,
|
|
4380
|
+
handleKeyDown,
|
|
4381
|
+
handlePaste
|
|
4382
|
+
},
|
|
4383
|
+
|
|
4384
|
+
appendTransaction(_, oldState, state) {
|
|
4385
|
+
return normalizeSelection(state, fixTables(state, oldState), allowTableNodeSelection);
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
});
|
|
4389
|
+
}
|
|
8
4390
|
|
|
9
4391
|
function updateColumns(node, colgroup, table, cellMinWidth, overrideCol, overrideValue) {
|
|
10
4392
|
let totalWidth = 0;
|
|
@@ -115,7 +4497,7 @@ function createTable(schema, rowsCount, colsCount, withHeaderRow, cellContent) {
|
|
|
115
4497
|
}
|
|
116
4498
|
|
|
117
4499
|
function isCellSelection(value) {
|
|
118
|
-
return value instanceof
|
|
4500
|
+
return value instanceof CellSelection;
|
|
119
4501
|
}
|
|
120
4502
|
|
|
121
4503
|
const deleteTableWhenAllCellsSelected = ({ editor }) => {
|
|
@@ -183,65 +4565,65 @@ const Table = core.Node.create({
|
|
|
183
4565
|
return true;
|
|
184
4566
|
},
|
|
185
4567
|
addColumnBefore: () => ({ state, dispatch }) => {
|
|
186
|
-
return
|
|
4568
|
+
return addColumnBefore(state, dispatch);
|
|
187
4569
|
},
|
|
188
4570
|
addColumnAfter: () => ({ state, dispatch }) => {
|
|
189
|
-
return
|
|
4571
|
+
return addColumnAfter(state, dispatch);
|
|
190
4572
|
},
|
|
191
4573
|
deleteColumn: () => ({ state, dispatch }) => {
|
|
192
|
-
return
|
|
4574
|
+
return deleteColumn(state, dispatch);
|
|
193
4575
|
},
|
|
194
4576
|
addRowBefore: () => ({ state, dispatch }) => {
|
|
195
|
-
return
|
|
4577
|
+
return addRowBefore(state, dispatch);
|
|
196
4578
|
},
|
|
197
4579
|
addRowAfter: () => ({ state, dispatch }) => {
|
|
198
|
-
return
|
|
4580
|
+
return addRowAfter(state, dispatch);
|
|
199
4581
|
},
|
|
200
4582
|
deleteRow: () => ({ state, dispatch }) => {
|
|
201
|
-
return
|
|
4583
|
+
return deleteRow(state, dispatch);
|
|
202
4584
|
},
|
|
203
4585
|
deleteTable: () => ({ state, dispatch }) => {
|
|
204
|
-
return
|
|
4586
|
+
return deleteTable(state, dispatch);
|
|
205
4587
|
},
|
|
206
4588
|
mergeCells: () => ({ state, dispatch }) => {
|
|
207
|
-
return
|
|
4589
|
+
return mergeCells(state, dispatch);
|
|
208
4590
|
},
|
|
209
4591
|
splitCell: () => ({ state, dispatch }) => {
|
|
210
|
-
return
|
|
4592
|
+
return splitCell(state, dispatch);
|
|
211
4593
|
},
|
|
212
4594
|
toggleHeaderColumn: () => ({ state, dispatch }) => {
|
|
213
|
-
return
|
|
4595
|
+
return toggleHeader('column')(state, dispatch);
|
|
214
4596
|
},
|
|
215
4597
|
toggleHeaderRow: () => ({ state, dispatch }) => {
|
|
216
|
-
return
|
|
4598
|
+
return toggleHeader('row')(state, dispatch);
|
|
217
4599
|
},
|
|
218
4600
|
toggleHeaderCell: () => ({ state, dispatch }) => {
|
|
219
|
-
return
|
|
4601
|
+
return toggleHeaderCell(state, dispatch);
|
|
220
4602
|
},
|
|
221
4603
|
mergeOrSplit: () => ({ state, dispatch }) => {
|
|
222
|
-
if (
|
|
4604
|
+
if (mergeCells(state, dispatch)) {
|
|
223
4605
|
return true;
|
|
224
4606
|
}
|
|
225
|
-
return
|
|
4607
|
+
return splitCell(state, dispatch);
|
|
226
4608
|
},
|
|
227
4609
|
setCellAttribute: (name, value) => ({ state, dispatch }) => {
|
|
228
|
-
return
|
|
4610
|
+
return setCellAttr(name, value)(state, dispatch);
|
|
229
4611
|
},
|
|
230
4612
|
goToNextCell: () => ({ state, dispatch }) => {
|
|
231
|
-
return
|
|
4613
|
+
return goToNextCell(1)(state, dispatch);
|
|
232
4614
|
},
|
|
233
4615
|
goToPreviousCell: () => ({ state, dispatch }) => {
|
|
234
|
-
return
|
|
4616
|
+
return goToNextCell(-1)(state, dispatch);
|
|
235
4617
|
},
|
|
236
4618
|
fixTables: () => ({ state, dispatch }) => {
|
|
237
4619
|
if (dispatch) {
|
|
238
|
-
|
|
4620
|
+
fixTables(state);
|
|
239
4621
|
}
|
|
240
4622
|
return true;
|
|
241
4623
|
},
|
|
242
4624
|
setCellSelection: position => ({ tr, dispatch }) => {
|
|
243
4625
|
if (dispatch) {
|
|
244
|
-
const selection =
|
|
4626
|
+
const selection = CellSelection.create(tr.doc, position.anchorCell, position.headCell);
|
|
245
4627
|
// @ts-ignore
|
|
246
4628
|
tr.setSelection(selection);
|
|
247
4629
|
}
|
|
@@ -274,7 +4656,7 @@ const Table = core.Node.create({
|
|
|
274
4656
|
addProseMirrorPlugins() {
|
|
275
4657
|
const isResizable = this.options.resizable && this.editor.isEditable;
|
|
276
4658
|
return [
|
|
277
|
-
...(isResizable ? [
|
|
4659
|
+
...(isResizable ? [columnResizing({
|
|
278
4660
|
handleWidth: this.options.handleWidth,
|
|
279
4661
|
cellMinWidth: this.options.cellMinWidth,
|
|
280
4662
|
View: this.options.View,
|
|
@@ -282,7 +4664,7 @@ const Table = core.Node.create({
|
|
|
282
4664
|
// @ts-ignore (incorrect type)
|
|
283
4665
|
lastColumnResizable: this.options.lastColumnResizable,
|
|
284
4666
|
})] : []),
|
|
285
|
-
|
|
4667
|
+
tableEditing({
|
|
286
4668
|
allowTableNodeSelection: this.options.allowTableNodeSelection,
|
|
287
4669
|
}),
|
|
288
4670
|
];
|