prosemirror-view 1.23.13 → 1.24.0-beta.3
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/index.cjs +5733 -0
- package/dist/index.d.ts +799 -0
- package/dist/index.es.js +41 -41
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +4823 -5139
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
- package/src/README.md +2 -0
- package/src/browser.ts +23 -0
- package/src/{capturekeys.js → capturekeys.ts} +38 -37
- package/src/{clipboard.js → clipboard.ts} +44 -41
- package/src/{decoration.js → decoration.ts} +313 -268
- package/src/{dom.js → dom.ts} +27 -24
- package/src/{domchange.js → domchange.ts} +38 -36
- package/src/{domcoords.js → domcoords.ts} +120 -99
- package/src/{domobserver.js → domobserver.ts} +41 -28
- package/src/index.ts +705 -0
- package/src/{input.js → input.ts} +203 -183
- package/src/{selection.js → selection.ts} +54 -48
- package/src/{viewdesc.js → viewdesc.ts} +342 -336
- package/rollup.config.js +0 -14
- package/src/browser.js +0 -23
- package/src/index.js +0 -668
package/src/{dom.js → dom.ts}
RENAMED
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import browser from "./browser"
|
|
1
|
+
import * as browser from "./browser"
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export type DOMNode = InstanceType<typeof window.Node>
|
|
4
|
+
export type DOMSelection = InstanceType<typeof window.Selection>
|
|
5
|
+
|
|
6
|
+
export const domIndex = function(node: Node) {
|
|
4
7
|
for (var index = 0;; index++) {
|
|
5
|
-
node = node.previousSibling
|
|
8
|
+
node = node.previousSibling!
|
|
6
9
|
if (!node) return index
|
|
7
10
|
}
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
export const parentNode = function(node) {
|
|
11
|
-
let parent = node.assignedSlot || node.parentNode
|
|
12
|
-
return parent && parent.nodeType == 11 ? parent.host : parent
|
|
13
|
+
export const parentNode = function(node: Node) {
|
|
14
|
+
let parent = (node as HTMLSlotElement).assignedSlot || node.parentNode
|
|
15
|
+
return parent && parent.nodeType == 11 ? (parent as ShadowRoot).host : parent
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
let reusedRange = null
|
|
18
|
+
let reusedRange: Range | null = null
|
|
16
19
|
|
|
17
20
|
// Note that this will always return the same range, because DOM range
|
|
18
21
|
// objects are every expensive, and keep slowing down subsequent DOM
|
|
19
22
|
// updates, for some reason.
|
|
20
|
-
export const textRange = function(node, from, to) {
|
|
23
|
+
export const textRange = function(node: Text, from?: number, to?: number) {
|
|
21
24
|
let range = reusedRange || (reusedRange = document.createRange())
|
|
22
|
-
range.setEnd(node, to == null ? node.nodeValue
|
|
25
|
+
range.setEnd(node, to == null ? node.nodeValue!.length : to)
|
|
23
26
|
range.setStart(node, from || 0)
|
|
24
27
|
return range
|
|
25
28
|
}
|
|
@@ -27,26 +30,26 @@ export const textRange = function(node, from, to) {
|
|
|
27
30
|
// Scans forward and backward through DOM positions equivalent to the
|
|
28
31
|
// given one to see if the two are in the same place (i.e. after a
|
|
29
32
|
// text node vs at the end of that text node)
|
|
30
|
-
export const isEquivalentPosition = function(node, off, targetNode, targetOff) {
|
|
33
|
+
export const isEquivalentPosition = function(node: Node, off: number, targetNode: Node, targetOff: number) {
|
|
31
34
|
return targetNode && (scanFor(node, off, targetNode, targetOff, -1) ||
|
|
32
35
|
scanFor(node, off, targetNode, targetOff, 1))
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
const atomElements = /^(img|br|input|textarea|hr)$/i
|
|
36
39
|
|
|
37
|
-
function scanFor(node, off, targetNode, targetOff, dir) {
|
|
40
|
+
function scanFor(node: Node, off: number, targetNode: Node, targetOff: number, dir: number) {
|
|
38
41
|
for (;;) {
|
|
39
42
|
if (node == targetNode && off == targetOff) return true
|
|
40
43
|
if (off == (dir < 0 ? 0 : nodeSize(node))) {
|
|
41
44
|
let parent = node.parentNode
|
|
42
45
|
if (!parent || parent.nodeType != 1 || hasBlockDesc(node) || atomElements.test(node.nodeName) ||
|
|
43
|
-
node.contentEditable == "false")
|
|
46
|
+
(node as HTMLElement).contentEditable == "false")
|
|
44
47
|
return false
|
|
45
48
|
off = domIndex(node) + (dir < 0 ? 0 : 1)
|
|
46
49
|
node = parent
|
|
47
50
|
} else if (node.nodeType == 1) {
|
|
48
51
|
node = node.childNodes[off + (dir < 0 ? -1 : 0)]
|
|
49
|
-
if (node.contentEditable == "false") return false
|
|
52
|
+
if ((node as HTMLElement).contentEditable == "false") return false
|
|
50
53
|
off = dir < 0 ? nodeSize(node) : 0
|
|
51
54
|
} else {
|
|
52
55
|
return false
|
|
@@ -54,40 +57,40 @@ function scanFor(node, off, targetNode, targetOff, dir) {
|
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
export function nodeSize(node) {
|
|
58
|
-
return node.nodeType == 3 ? node.nodeValue
|
|
60
|
+
export function nodeSize(node: Node) {
|
|
61
|
+
return node.nodeType == 3 ? node.nodeValue!.length : node.childNodes.length
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
export function isOnEdge(node, offset, parent) {
|
|
64
|
+
export function isOnEdge(node: Node, offset: number, parent: Node) {
|
|
62
65
|
for (let atStart = offset == 0, atEnd = offset == nodeSize(node); atStart || atEnd;) {
|
|
63
66
|
if (node == parent) return true
|
|
64
67
|
let index = domIndex(node)
|
|
65
|
-
node = node.parentNode
|
|
68
|
+
node = node.parentNode!
|
|
66
69
|
if (!node) return false
|
|
67
70
|
atStart = atStart && index == 0
|
|
68
71
|
atEnd = atEnd && index == nodeSize(node)
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
function hasBlockDesc(dom) {
|
|
75
|
+
function hasBlockDesc(dom: Node) {
|
|
73
76
|
let desc
|
|
74
|
-
for (let cur = dom; cur; cur = cur.parentNode) if (desc = cur.pmViewDesc) break
|
|
77
|
+
for (let cur: Node | null = dom; cur; cur = cur.parentNode) if (desc = cur.pmViewDesc) break
|
|
75
78
|
return desc && desc.node && desc.node.isBlock && (desc.dom == dom || desc.contentDOM == dom)
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
// Work around Chrome issue https://bugs.chromium.org/p/chromium/issues/detail?id=447523
|
|
79
82
|
// (isCollapsed inappropriately returns true in shadow dom)
|
|
80
|
-
export const selectionCollapsed = function(domSel) {
|
|
83
|
+
export const selectionCollapsed = function(domSel: Selection) {
|
|
81
84
|
let collapsed = domSel.isCollapsed
|
|
82
85
|
if (collapsed && browser.chrome && domSel.rangeCount && !domSel.getRangeAt(0).collapsed)
|
|
83
86
|
collapsed = false
|
|
84
87
|
return collapsed
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
export function keyEvent(keyCode, key) {
|
|
88
|
-
let event = document.createEvent("Event")
|
|
90
|
+
export function keyEvent(keyCode: number, key: string) {
|
|
91
|
+
let event = document.createEvent("Event") as KeyboardEvent
|
|
89
92
|
event.initEvent("keydown", true, true)
|
|
90
|
-
event.keyCode = keyCode
|
|
91
|
-
event.key = event.code = key
|
|
93
|
+
;(event as any).keyCode = keyCode
|
|
94
|
+
;(event as any).key = (event as any).code = key
|
|
92
95
|
return event
|
|
93
96
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {Fragment, DOMParser} from "prosemirror-model"
|
|
1
|
+
import {Fragment, DOMParser, ParseRule, Node, Mark, ResolvedPos} from "prosemirror-model"
|
|
2
2
|
import {Selection, TextSelection} from "prosemirror-state"
|
|
3
3
|
|
|
4
4
|
import {selectionBetween, selectionFromDOM, selectionToDOM} from "./selection"
|
|
5
|
-
import {selectionCollapsed, keyEvent} from "./dom"
|
|
6
|
-
import browser from "./browser"
|
|
5
|
+
import {selectionCollapsed, keyEvent, DOMNode} from "./dom"
|
|
6
|
+
import * as browser from "./browser"
|
|
7
|
+
import {EditorView} from "./index"
|
|
7
8
|
|
|
8
9
|
// Note that all referencing and parsing is done with the
|
|
9
10
|
// start-of-operation selection and document, since that's the one
|
|
@@ -11,18 +12,20 @@ import browser from "./browser"
|
|
|
11
12
|
// the modification is mapped over those before it is applied, in
|
|
12
13
|
// readDOMChange.
|
|
13
14
|
|
|
14
|
-
function parseBetween(view, from_, to_) {
|
|
15
|
+
function parseBetween(view: EditorView, from_: number, to_: number) {
|
|
15
16
|
let {node: parent, fromOffset, toOffset, from, to} = view.docView.parseRange(from_, to_)
|
|
16
17
|
|
|
17
|
-
let domSel = view.
|
|
18
|
+
let domSel = view.domSelection()
|
|
19
|
+
let find: {node: DOMNode, offset: number, pos?: number}[] | undefined
|
|
20
|
+
let anchor = domSel.anchorNode
|
|
18
21
|
if (anchor && view.dom.contains(anchor.nodeType == 1 ? anchor : anchor.parentNode)) {
|
|
19
22
|
find = [{node: anchor, offset: domSel.anchorOffset}]
|
|
20
23
|
if (!selectionCollapsed(domSel))
|
|
21
|
-
find.push({node: domSel.focusNode
|
|
24
|
+
find.push({node: domSel.focusNode!, offset: domSel.focusOffset})
|
|
22
25
|
}
|
|
23
26
|
// Work around issue in Chrome where backspacing sometimes replaces
|
|
24
27
|
// the deleted content with a random BR node (issues #799, #831)
|
|
25
|
-
if (browser.chrome && view.lastKeyCode === 8) {
|
|
28
|
+
if (browser.chrome && view.input.lastKeyCode === 8) {
|
|
26
29
|
for (let off = toOffset; off > fromOffset; off--) {
|
|
27
30
|
let node = parent.childNodes[off - 1], desc = node.pmViewDesc
|
|
28
31
|
if (node.nodeName == "BR" && !desc) { toOffset = off; break }
|
|
@@ -40,7 +43,6 @@ function parseBetween(view, from_, to_) {
|
|
|
40
43
|
from: fromOffset,
|
|
41
44
|
to: toOffset,
|
|
42
45
|
preserveWhitespace: $from.parent.type.whitespace == "pre" ? "full" : true,
|
|
43
|
-
editableContent: true,
|
|
44
46
|
findPositions: find,
|
|
45
47
|
ruleFromNode,
|
|
46
48
|
context: $from
|
|
@@ -53,7 +55,7 @@ function parseBetween(view, from_, to_) {
|
|
|
53
55
|
return {doc, sel, from, to}
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
function ruleFromNode(dom) {
|
|
58
|
+
function ruleFromNode(dom: DOMNode): ParseRule | null {
|
|
57
59
|
let desc = dom.pmViewDesc
|
|
58
60
|
if (desc) {
|
|
59
61
|
return desc.parseRule()
|
|
@@ -64,18 +66,19 @@ function ruleFromNode(dom) {
|
|
|
64
66
|
if (browser.safari && /^(ul|ol)$/i.test(dom.parentNode.nodeName)) {
|
|
65
67
|
let skip = document.createElement("div")
|
|
66
68
|
skip.appendChild(document.createElement("li"))
|
|
67
|
-
return {skip}
|
|
69
|
+
return {skip} as any
|
|
68
70
|
} else if (dom.parentNode.lastChild == dom || browser.safari && /^(tr|table)$/i.test(dom.parentNode.nodeName)) {
|
|
69
71
|
return {ignore: true}
|
|
70
72
|
}
|
|
71
|
-
} else if (dom.nodeName == "IMG" && dom.getAttribute("mark-placeholder")) {
|
|
73
|
+
} else if (dom.nodeName == "IMG" && (dom as HTMLElement).getAttribute("mark-placeholder")) {
|
|
72
74
|
return {ignore: true}
|
|
73
75
|
}
|
|
76
|
+
return null
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
export function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
79
|
+
export function readDOMChange(view: EditorView, from: number, to: number, typeOver: boolean, addedNodes: readonly DOMNode[]) {
|
|
77
80
|
if (from < 0) {
|
|
78
|
-
let origin = view.lastSelectionTime > Date.now() - 50 ? view.lastSelectionOrigin : null
|
|
81
|
+
let origin = view.input.lastSelectionTime > Date.now() - 50 ? view.input.lastSelectionOrigin : null
|
|
79
82
|
let newSel = selectionFromDOM(view, origin)
|
|
80
83
|
if (newSel && !view.state.selection.eq(newSel)) {
|
|
81
84
|
let tr = view.state.tr.setSelection(newSel)
|
|
@@ -97,29 +100,29 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
97
100
|
// Chrome sometimes leaves the cursor before the inserted text when
|
|
98
101
|
// composing after a cursor wrapper. This moves it forward.
|
|
99
102
|
if (browser.chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from) {
|
|
100
|
-
let text = view.cursorWrapper.deco.type.toDOM.nextSibling
|
|
103
|
+
let text = (view.cursorWrapper.deco.type as any).toDOM.nextSibling as DOMNode
|
|
101
104
|
let size = text && text.nodeValue ? text.nodeValue.length : 1
|
|
102
105
|
parse.sel = {anchor: parse.sel.anchor + size, head: parse.sel.anchor + size}
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
let doc = view.state.doc, compare = doc.slice(parse.from, parse.to)
|
|
106
|
-
let preferredPos, preferredSide
|
|
109
|
+
let preferredPos, preferredSide: "start" | "end"
|
|
107
110
|
// Prefer anchoring to end when Backspace is pressed
|
|
108
|
-
if (view.lastKeyCode === 8 && Date.now() - 100 < view.lastKeyCodeTime) {
|
|
111
|
+
if (view.input.lastKeyCode === 8 && Date.now() - 100 < view.input.lastKeyCodeTime) {
|
|
109
112
|
preferredPos = view.state.selection.to
|
|
110
113
|
preferredSide = "end"
|
|
111
114
|
} else {
|
|
112
115
|
preferredPos = view.state.selection.from
|
|
113
116
|
preferredSide = "start"
|
|
114
117
|
}
|
|
115
|
-
view.lastKeyCode = null
|
|
118
|
+
view.input.lastKeyCode = null
|
|
116
119
|
|
|
117
120
|
let change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide)
|
|
118
|
-
if ((browser.ios && view.lastIOSEnter > Date.now() - 225 || browser.android) &&
|
|
121
|
+
if ((browser.ios && view.input.lastIOSEnter > Date.now() - 225 || browser.android) &&
|
|
119
122
|
addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P") &&
|
|
120
123
|
(!change || change.endA >= change.endB) &&
|
|
121
124
|
view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
|
|
122
|
-
view.lastIOSEnter = 0
|
|
125
|
+
view.input.lastIOSEnter = 0
|
|
123
126
|
return
|
|
124
127
|
}
|
|
125
128
|
if (!change) {
|
|
@@ -134,7 +137,7 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
134
137
|
return
|
|
135
138
|
}
|
|
136
139
|
}
|
|
137
|
-
view.domChangeCount++
|
|
140
|
+
view.input.domChangeCount++
|
|
138
141
|
// Handle the case where overwriting a selection by typing matches
|
|
139
142
|
// the start or end of the selected content, creating a change
|
|
140
143
|
// that's smaller than what was actually overwritten.
|
|
@@ -168,13 +171,13 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
168
171
|
let nextSel
|
|
169
172
|
// If this looks like the effect of pressing Enter (or was recorded
|
|
170
173
|
// as being an iOS enter press), just dispatch an Enter key instead.
|
|
171
|
-
if (((browser.ios && view.lastIOSEnter > Date.now() - 225 &&
|
|
174
|
+
if (((browser.ios && view.input.lastIOSEnter > Date.now() - 225 &&
|
|
172
175
|
(!inlineChange || addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P"))) ||
|
|
173
176
|
(!inlineChange && $from.pos < parse.doc.content.size &&
|
|
174
177
|
(nextSel = Selection.findFrom(parse.doc.resolve($from.pos + 1), 1, true)) &&
|
|
175
178
|
nextSel.head == $to.pos)) &&
|
|
176
179
|
view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
|
|
177
|
-
view.lastIOSEnter = 0
|
|
180
|
+
view.input.lastIOSEnter = 0
|
|
178
181
|
return
|
|
179
182
|
}
|
|
180
183
|
// Same for backspace
|
|
@@ -188,8 +191,8 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
188
191
|
// Chrome Android will occasionally, during composition, delete the
|
|
189
192
|
// entire composition and then immediately insert it again. This is
|
|
190
193
|
// used to detect that situation.
|
|
191
|
-
if (browser.chrome && browser.android && change.
|
|
192
|
-
view.lastAndroidDelete = Date.now()
|
|
194
|
+
if (browser.chrome && browser.android && change.endB == change.start)
|
|
195
|
+
view.input.lastAndroidDelete = Date.now()
|
|
193
196
|
|
|
194
197
|
// This tries to detect Android virtual keyboard
|
|
195
198
|
// enter-and-pick-suggestion action. That sometimes (see issue
|
|
@@ -247,7 +250,7 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
247
250
|
// Edge just doesn't move the cursor forward when you start typing
|
|
248
251
|
// in an empty block or between br nodes.
|
|
249
252
|
if (sel && !(browser.chrome && browser.android && view.composing && sel.empty &&
|
|
250
|
-
(change.start != change.endB || view.lastAndroidDelete < Date.now() - 100) &&
|
|
253
|
+
(change.start != change.endB || view.input.lastAndroidDelete < Date.now() - 100) &&
|
|
251
254
|
(sel.head == chFrom || sel.head == tr.mapping.map(chTo) - 1) ||
|
|
252
255
|
browser.ie && sel.empty && sel.head == chFrom))
|
|
253
256
|
tr.setSelection(sel)
|
|
@@ -256,28 +259,27 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
256
259
|
view.dispatch(tr.scrollIntoView())
|
|
257
260
|
}
|
|
258
261
|
|
|
259
|
-
function resolveSelection(view, doc, parsedSel) {
|
|
262
|
+
function resolveSelection(view: EditorView, doc: Node, parsedSel: {anchor: number, head: number}) {
|
|
260
263
|
if (Math.max(parsedSel.anchor, parsedSel.head) > doc.content.size) return null
|
|
261
264
|
return selectionBetween(view, doc.resolve(parsedSel.anchor), doc.resolve(parsedSel.head))
|
|
262
265
|
}
|
|
263
266
|
|
|
264
|
-
// : (Fragment, Fragment) → ?{mark: Mark, type: string}
|
|
265
267
|
// Given two same-length, non-empty fragments of inline content,
|
|
266
268
|
// determine whether the first could be created from the second by
|
|
267
269
|
// removing or adding a single mark type.
|
|
268
|
-
function isMarkChange(cur, prev) {
|
|
269
|
-
let curMarks = cur.firstChild
|
|
270
|
-
let added = curMarks, removed = prevMarks, type, mark, update
|
|
270
|
+
function isMarkChange(cur: Fragment, prev: Fragment) {
|
|
271
|
+
let curMarks = cur.firstChild!.marks, prevMarks = prev.firstChild!.marks
|
|
272
|
+
let added = curMarks, removed = prevMarks, type, mark: Mark | undefined, update
|
|
271
273
|
for (let i = 0; i < prevMarks.length; i++) added = prevMarks[i].removeFromSet(added)
|
|
272
274
|
for (let i = 0; i < curMarks.length; i++) removed = curMarks[i].removeFromSet(removed)
|
|
273
275
|
if (added.length == 1 && removed.length == 0) {
|
|
274
276
|
mark = added[0]
|
|
275
277
|
type = "add"
|
|
276
|
-
update = node => node.mark(mark
|
|
278
|
+
update = (node: Node) => node.mark(mark!.addToSet(node.marks))
|
|
277
279
|
} else if (added.length == 0 && removed.length == 1) {
|
|
278
280
|
mark = removed[0]
|
|
279
281
|
type = "remove"
|
|
280
|
-
update = node => node.mark(mark
|
|
282
|
+
update = (node: Node) => node.mark(mark!.removeFromSet(node.marks))
|
|
281
283
|
} else {
|
|
282
284
|
return null
|
|
283
285
|
}
|
|
@@ -286,7 +288,7 @@ function isMarkChange(cur, prev) {
|
|
|
286
288
|
if (Fragment.from(updated).eq(cur)) return {mark, type}
|
|
287
289
|
}
|
|
288
290
|
|
|
289
|
-
function looksLikeJoin(old, start, end, $newStart, $newEnd) {
|
|
291
|
+
function looksLikeJoin(old: Node, start: number, end: number, $newStart: ResolvedPos, $newEnd: ResolvedPos) {
|
|
290
292
|
if (!$newStart.parent.isTextblock ||
|
|
291
293
|
// The content must have shrunk
|
|
292
294
|
end - start <= $newEnd.pos - $newStart.pos ||
|
|
@@ -308,7 +310,7 @@ function looksLikeJoin(old, start, end, $newStart, $newEnd) {
|
|
|
308
310
|
return $newStart.parent.content.cut($newStart.parentOffset).eq($next.parent.content)
|
|
309
311
|
}
|
|
310
312
|
|
|
311
|
-
function skipClosingAndOpening($pos, fromEnd, mayOpen) {
|
|
313
|
+
function skipClosingAndOpening($pos: ResolvedPos, fromEnd: boolean, mayOpen: boolean) {
|
|
312
314
|
let depth = $pos.depth, end = fromEnd ? $pos.end() : $pos.pos
|
|
313
315
|
while (depth > 0 && (fromEnd || $pos.indexAfter(depth) == $pos.node(depth).childCount)) {
|
|
314
316
|
depth--
|
|
@@ -325,10 +327,10 @@ function skipClosingAndOpening($pos, fromEnd, mayOpen) {
|
|
|
325
327
|
return end
|
|
326
328
|
}
|
|
327
329
|
|
|
328
|
-
function findDiff(a, b, pos, preferredPos, preferredSide) {
|
|
330
|
+
function findDiff(a: Fragment, b: Fragment, pos: number, preferredPos: number, preferredSide: "start" | "end") {
|
|
329
331
|
let start = a.findDiffStart(b, pos)
|
|
330
332
|
if (start == null) return null
|
|
331
|
-
let {a: endA, b: endB} = a.findDiffEnd(b, pos + a.size, pos + b.size)
|
|
333
|
+
let {a: endA, b: endB} = a.findDiffEnd(b, pos + a.size, pos + b.size)!
|
|
332
334
|
if (preferredSide == "end") {
|
|
333
335
|
let adjust = Math.max(0, start - Math.min(endA, endB))
|
|
334
336
|
preferredPos -= endA + adjust - start
|