prosemirror-transform 1.4.0 → 1.5.0-beta.1

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.
@@ -1,33 +1,31 @@
1
- import {Slice} from "prosemirror-model"
1
+ import {Slice, Node, Schema} from "prosemirror-model"
2
2
 
3
3
  import {Step, StepResult} from "./step"
4
- import {StepMap} from "./map"
4
+ import {StepMap, Mappable} from "./map"
5
5
 
6
- // ::- Replace a part of the document with a slice of new content.
6
+ /// Replace a part of the document with a slice of new content.
7
7
  export class ReplaceStep extends Step {
8
- // :: (number, number, Slice, ?bool)
9
- // The given `slice` should fit the 'gap' between `from` and
10
- // `to`—the depths must line up, and the surrounding nodes must be
11
- // able to be joined with the open sides of the slice. When
12
- // `structure` is true, the step will fail if the content between
13
- // from and to is not just a sequence of closing and then opening
14
- // tokens (this is to guard against rebased replace steps
15
- // overwriting something they weren't supposed to).
16
- constructor(from, to, slice, structure) {
8
+ /// The given `slice` should fit the 'gap' between `from` and
9
+ /// `to`—the depths must line up, and the surrounding nodes must be
10
+ /// able to be joined with the open sides of the slice. When
11
+ /// `structure` is true, the step will fail if the content between
12
+ /// from and to is not just a sequence of closing and then opening
13
+ /// tokens (this is to guard against rebased replace steps
14
+ /// overwriting something they weren't supposed to).
15
+ constructor(
16
+ /// The start position of the replaced range.
17
+ readonly from: number,
18
+ /// The end position of the replaced range.
19
+ readonly to: number,
20
+ /// The slice to insert.
21
+ readonly slice: Slice,
22
+ /// @internal
23
+ readonly structure = false
24
+ ) {
17
25
  super()
18
- // :: number
19
- // The start position of the replaced range.
20
- this.from = from
21
- // :: number
22
- // The end position of the replaced range.
23
- this.to = to
24
- // :: Slice
25
- // The slice to insert.
26
- this.slice = slice
27
- this.structure = !!structure
28
- }
29
-
30
- apply(doc) {
26
+ }
27
+
28
+ apply(doc: Node) {
31
29
  if (this.structure && contentBetween(doc, this.from, this.to))
32
30
  return StepResult.fail("Structure replace would overwrite content")
33
31
  return StepResult.fromReplace(doc, this.from, this.to, this.slice)
@@ -37,17 +35,17 @@ export class ReplaceStep extends Step {
37
35
  return new StepMap([this.from, this.to - this.from, this.slice.size])
38
36
  }
39
37
 
40
- invert(doc) {
38
+ invert(doc: Node) {
41
39
  return new ReplaceStep(this.from, this.from + this.slice.size, doc.slice(this.from, this.to))
42
40
  }
43
41
 
44
- map(mapping) {
42
+ map(mapping: Mappable) {
45
43
  let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
46
44
  if (from.deleted && to.deleted) return null
47
45
  return new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice)
48
46
  }
49
47
 
50
- merge(other) {
48
+ merge(other: Step) {
51
49
  if (!(other instanceof ReplaceStep) || other.structure || this.structure) return null
52
50
 
53
51
  if (this.from + this.slice.size == other.from && !this.slice.openEnd && !other.slice.openStart) {
@@ -63,14 +61,15 @@ export class ReplaceStep extends Step {
63
61
  }
64
62
  }
65
63
 
66
- toJSON() {
67
- let json = {stepType: "replace", from: this.from, to: this.to}
64
+ toJSON(): any {
65
+ let json: any = {stepType: "replace", from: this.from, to: this.to}
68
66
  if (this.slice.size) json.slice = this.slice.toJSON()
69
67
  if (this.structure) json.structure = true
70
68
  return json
71
69
  }
72
70
 
73
- static fromJSON(schema, json) {
71
+ /// @internal
72
+ static fromJSON(schema: Schema, json: any) {
74
73
  if (typeof json.from != "number" || typeof json.to != "number")
75
74
  throw new RangeError("Invalid input for ReplaceStep.fromJSON")
76
75
  return new ReplaceStep(json.from, json.to, Slice.fromJSON(schema, json.slice), !!json.structure)
@@ -79,40 +78,35 @@ export class ReplaceStep extends Step {
79
78
 
80
79
  Step.jsonID("replace", ReplaceStep)
81
80
 
82
- // ::- Replace a part of the document with a slice of content, but
83
- // preserve a range of the replaced content by moving it into the
84
- // slice.
81
+ /// Replace a part of the document with a slice of content, but
82
+ /// preserve a range of the replaced content by moving it into the
83
+ /// slice.
85
84
  export class ReplaceAroundStep extends Step {
86
- // :: (number, number, number, number, Slice, number, ?bool)
87
- // Create a replace-around step with the given range and gap.
88
- // `insert` should be the point in the slice into which the content
89
- // of the gap should be moved. `structure` has the same meaning as
90
- // it has in the [`ReplaceStep`](#transform.ReplaceStep) class.
91
- constructor(from, to, gapFrom, gapTo, slice, insert, structure) {
85
+ /// Create a replace-around step with the given range and gap.
86
+ /// `insert` should be the point in the slice into which the content
87
+ /// of the gap should be moved. `structure` has the same meaning as
88
+ /// it has in the [`ReplaceStep`](#transform.ReplaceStep) class.
89
+ constructor(
90
+ /// The start position of the replaced range.
91
+ readonly from: number,
92
+ /// The end position of the replaced range.
93
+ readonly to: number,
94
+ /// The start of preserved range.
95
+ readonly gapFrom: number,
96
+ /// The end of preserved range.
97
+ readonly gapTo: number,
98
+ /// The slice to insert.
99
+ readonly slice: Slice,
100
+ /// The position in the slice where the preserved range should be
101
+ /// inserted.
102
+ readonly insert: number,
103
+ /// @internal
104
+ readonly structure = false
105
+ ) {
92
106
  super()
93
- // :: number
94
- // The start position of the replaced range.
95
- this.from = from
96
- // :: number
97
- // The end position of the replaced range.
98
- this.to = to
99
- // :: number
100
- // The start of preserved range.
101
- this.gapFrom = gapFrom
102
- // :: number
103
- // The end of preserved range.
104
- this.gapTo = gapTo
105
- // :: Slice
106
- // The slice to insert.
107
- this.slice = slice
108
- // :: number
109
- // The position in the slice where the preserved range should be
110
- // inserted.
111
- this.insert = insert
112
- this.structure = !!structure
113
- }
114
-
115
- apply(doc) {
107
+ }
108
+
109
+ apply(doc: Node) {
116
110
  if (this.structure && (contentBetween(doc, this.from, this.gapFrom) ||
117
111
  contentBetween(doc, this.gapTo, this.to)))
118
112
  return StepResult.fail("Structure gap-replace would overwrite content")
@@ -130,7 +124,7 @@ export class ReplaceAroundStep extends Step {
130
124
  this.gapTo, this.to - this.gapTo, this.slice.size - this.insert])
131
125
  }
132
126
 
133
- invert(doc) {
127
+ invert(doc: Node) {
134
128
  let gap = this.gapTo - this.gapFrom
135
129
  return new ReplaceAroundStep(this.from, this.from + this.slice.size + gap,
136
130
  this.from + this.insert, this.from + this.insert + gap,
@@ -138,22 +132,23 @@ export class ReplaceAroundStep extends Step {
138
132
  this.gapFrom - this.from, this.structure)
139
133
  }
140
134
 
141
- map(mapping) {
135
+ map(mapping: Mappable) {
142
136
  let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
143
137
  let gapFrom = mapping.map(this.gapFrom, -1), gapTo = mapping.map(this.gapTo, 1)
144
138
  if ((from.deleted && to.deleted) || gapFrom < from.pos || gapTo > to.pos) return null
145
139
  return new ReplaceAroundStep(from.pos, to.pos, gapFrom, gapTo, this.slice, this.insert, this.structure)
146
140
  }
147
141
 
148
- toJSON() {
149
- let json = {stepType: "replaceAround", from: this.from, to: this.to,
150
- gapFrom: this.gapFrom, gapTo: this.gapTo, insert: this.insert}
142
+ toJSON(): any {
143
+ let json: any = {stepType: "replaceAround", from: this.from, to: this.to,
144
+ gapFrom: this.gapFrom, gapTo: this.gapTo, insert: this.insert}
151
145
  if (this.slice.size) json.slice = this.slice.toJSON()
152
146
  if (this.structure) json.structure = true
153
147
  return json
154
148
  }
155
149
 
156
- static fromJSON(schema, json) {
150
+ /// @internal
151
+ static fromJSON(schema: Schema, json: any) {
157
152
  if (typeof json.from != "number" || typeof json.to != "number" ||
158
153
  typeof json.gapFrom != "number" || typeof json.gapTo != "number" || typeof json.insert != "number")
159
154
  throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON")
@@ -164,7 +159,7 @@ export class ReplaceAroundStep extends Step {
164
159
 
165
160
  Step.jsonID("replaceAround", ReplaceAroundStep)
166
161
 
167
- function contentBetween(doc, from, to) {
162
+ function contentBetween(doc: Node, from: number, to: number) {
168
163
  let $from = doc.resolve(from), dist = to - from, depth = $from.depth
169
164
  while (dist > 0 && depth > 0 && $from.indexAfter(depth) == $from.node(depth).childCount) {
170
165
  depth--
package/src/step.ts ADDED
@@ -0,0 +1,97 @@
1
+ import {ReplaceError, Schema, Slice, Node} from "prosemirror-model"
2
+
3
+ import {StepMap, Mappable} from "./map"
4
+
5
+ const stepsByID: {[id: string]: {fromJSON(schema: Schema, json: any): Step}} = Object.create(null)
6
+
7
+ /// A step object represents an atomic change. It generally applies
8
+ /// only to the document it was created for, since the positions
9
+ /// stored in it will only make sense for that document.
10
+ ///
11
+ /// New steps are defined by creating classes that extend `Step`,
12
+ /// overriding the `apply`, `invert`, `map`, `getMap` and `fromJSON`
13
+ /// methods, and registering your class with a unique
14
+ /// JSON-serialization identifier using
15
+ /// [`Step.jsonID`](#transform.Step^jsonID).
16
+ export abstract class Step {
17
+ /// Applies this step to the given document, returning a result
18
+ /// object that either indicates failure, if the step can not be
19
+ /// applied to this document, or indicates success by containing a
20
+ /// transformed document.
21
+ abstract apply(doc: Node): StepResult
22
+
23
+ /// Get the step map that represents the changes made by this step,
24
+ /// and which can be used to transform between positions in the old
25
+ /// and the new document.
26
+ getMap(): StepMap { return StepMap.empty }
27
+
28
+ /// Create an inverted version of this step. Needs the document as it
29
+ /// was before the step as argument.
30
+ abstract invert(doc: Node): Step
31
+
32
+ /// Map this step through a mappable thing, returning either a
33
+ /// version of that step with its positions adjusted, or `null` if
34
+ /// the step was entirely deleted by the mapping.
35
+ abstract map(mapping: Mappable): Step | null
36
+
37
+ /// Try to merge this step with another one, to be applied directly
38
+ /// after it. Returns the merged step when possible, null if the
39
+ /// steps can't be merged.
40
+ merge(other: Step): Step | null { return null }
41
+
42
+ /// Create a JSON-serializeable representation of this step. When
43
+ /// defining this for a custom subclass, make sure the result object
44
+ /// includes the step type's [JSON id](#transform.Step^jsonID) under
45
+ /// the `stepType` property.
46
+ abstract toJSON(): any
47
+
48
+ /// Deserialize a step from its JSON representation. Will call
49
+ /// through to the step class' own implementation of this method.
50
+ static fromJSON(schema: Schema, json: any): Step {
51
+ if (!json || !json.stepType) throw new RangeError("Invalid input for Step.fromJSON")
52
+ let type = stepsByID[json.stepType]
53
+ if (!type) throw new RangeError(`No step type ${json.stepType} defined`)
54
+ return type.fromJSON(schema, json)
55
+ }
56
+
57
+ /// To be able to serialize steps to JSON, each step needs a string
58
+ /// ID to attach to its JSON representation. Use this method to
59
+ /// register an ID for your step classes. Try to pick something
60
+ /// that's unlikely to clash with steps from other modules.
61
+ static jsonID(id: string, stepClass: {fromJSON(schema: Schema, json: any): Step}) {
62
+ if (id in stepsByID) throw new RangeError("Duplicate use of step JSON ID " + id)
63
+ stepsByID[id] = stepClass
64
+ ;(stepClass as any).prototype.jsonID = id
65
+ return stepClass
66
+ }
67
+ }
68
+
69
+ /// The result of [applying](#transform.Step.apply) a step. Contains either a
70
+ /// new document or a failure value.
71
+ export class StepResult {
72
+ /// @internal
73
+ constructor(
74
+ /// The transformed document, if successful.
75
+ readonly doc: Node | null,
76
+ /// The failure message, if unsuccessful.
77
+ readonly failed: string | null
78
+ ) {}
79
+
80
+ /// Create a successful step result.
81
+ static ok(doc: Node) { return new StepResult(doc, null) }
82
+
83
+ /// Create a failed step result.
84
+ static fail(message: string) { return new StepResult(null, message) }
85
+
86
+ /// Call [`Node.replace`](#model.Node.replace) with the given
87
+ /// arguments. Create a successful result if it succeeds, and a
88
+ /// failed one if it throws a `ReplaceError`.
89
+ static fromReplace(doc: Node, from: number, to: number, slice: Slice) {
90
+ try {
91
+ return StepResult.ok(doc.replace(from, to, slice))
92
+ } catch (e) {
93
+ if (e instanceof ReplaceError) return StepResult.fail(e.message)
94
+ throw e
95
+ }
96
+ }
97
+ }
@@ -1,18 +1,17 @@
1
- import {Slice, Fragment} from "prosemirror-model"
1
+ import {Slice, Fragment, NodeRange, NodeType, Node, Mark, Attrs, ContentMatch} from "prosemirror-model"
2
2
 
3
3
  import {Transform} from "./transform"
4
4
  import {ReplaceStep, ReplaceAroundStep} from "./replace_step"
5
5
 
6
- function canCut(node, start, end) {
6
+ function canCut(node: Node, start: number, end: number) {
7
7
  return (start == 0 || node.canReplace(start, node.childCount)) &&
8
8
  (end == node.childCount || node.canReplace(0, end))
9
9
  }
10
10
 
11
- // :: (NodeRange) → ?number
12
- // Try to find a target depth to which the content in the given range
13
- // can be lifted. Will not go across
14
- // [isolating](#model.NodeSpec.isolating) parent nodes.
15
- export function liftTarget(range) {
11
+ /// Try to find a target depth to which the content in the given range
12
+ /// can be lifted. Will not go across
13
+ /// [isolating](#model.NodeSpec.isolating) parent nodes.
14
+ export function liftTarget(range: NodeRange): number | null {
16
15
  let parent = range.parent
17
16
  let content = parent.content.cutByIndex(range.startIndex, range.endIndex)
18
17
  for (let depth = range.depth;; --depth) {
@@ -22,15 +21,10 @@ export function liftTarget(range) {
22
21
  return depth
23
22
  if (depth == 0 || node.type.spec.isolating || !canCut(node, index, endIndex)) break
24
23
  }
24
+ return null
25
25
  }
26
26
 
27
- // :: (NodeRange, number) → this
28
- // Split the content in the given range off from its parent, if there
29
- // is sibling content before or after it, and move it up the tree to
30
- // the depth specified by `target`. You'll probably want to use
31
- // [`liftTarget`](#transform.liftTarget) to compute `target`, to make
32
- // sure the lift is valid.
33
- Transform.prototype.lift = function(range, target) {
27
+ export function lift(tr: Transform, range: NodeRange, target: number) {
34
28
  let {$from, $to, depth} = range
35
29
 
36
30
  let gapStart = $from.before(depth + 1), gapEnd = $to.after(depth + 1)
@@ -55,28 +49,33 @@ Transform.prototype.lift = function(range, target) {
55
49
  end++
56
50
  }
57
51
 
58
- return this.step(new ReplaceAroundStep(start, end, gapStart, gapEnd,
59
- new Slice(before.append(after), openStart, openEnd),
60
- before.size - openStart, true))
52
+ tr.step(new ReplaceAroundStep(start, end, gapStart, gapEnd,
53
+ new Slice(before.append(after), openStart, openEnd),
54
+ before.size - openStart, true))
61
55
  }
62
56
 
63
- // :: (NodeRange, NodeType, ?Object, ?NodeRange) → ?[{type: NodeType, attrs: ?Object}]
64
- // Try to find a valid way to wrap the content in the given range in a
65
- // node of the given type. May introduce extra nodes around and inside
66
- // the wrapper node, if necessary. Returns null if no valid wrapping
67
- // could be found. When `innerRange` is given, that range's content is
68
- // used as the content to fit into the wrapping, instead of the
69
- // content of `range`.
70
- export function findWrapping(range, nodeType, attrs, innerRange = range) {
57
+ /// Try to find a valid way to wrap the content in the given range in a
58
+ /// node of the given type. May introduce extra nodes around and inside
59
+ /// the wrapper node, if necessary. Returns null if no valid wrapping
60
+ /// could be found. When `innerRange` is given, that range's content is
61
+ /// used as the content to fit into the wrapping, instead of the
62
+ /// content of `range`.
63
+ export function findWrapping(
64
+ range: NodeRange,
65
+ nodeType: NodeType,
66
+ attrs: Attrs | null = null,
67
+ innerRange = range
68
+ ): {type: NodeType, attrs: Attrs | null}[] | null {
71
69
  let around = findWrappingOutside(range, nodeType)
72
70
  let inner = around && findWrappingInside(innerRange, nodeType)
73
71
  if (!inner) return null
74
- return around.map(withAttrs).concat({type: nodeType, attrs}).concat(inner.map(withAttrs))
72
+ return (around!.map(withAttrs) as {type: NodeType, attrs: Attrs | null}[])
73
+ .concat({type: nodeType, attrs}).concat(inner.map(withAttrs))
75
74
  }
76
75
 
77
- function withAttrs(type) { return {type, attrs: null} }
76
+ function withAttrs(type: NodeType) { return {type, attrs: null} }
78
77
 
79
- function findWrappingOutside(range, type) {
78
+ function findWrappingOutside(range: NodeRange, type: NodeType) {
80
79
  let {parent, startIndex, endIndex} = range
81
80
  let around = parent.contentMatchAt(startIndex).findWrapping(type)
82
81
  if (!around) return null
@@ -84,24 +83,20 @@ function findWrappingOutside(range, type) {
84
83
  return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null
85
84
  }
86
85
 
87
- function findWrappingInside(range, type) {
86
+ function findWrappingInside(range: NodeRange, type: NodeType) {
88
87
  let {parent, startIndex, endIndex} = range
89
88
  let inner = parent.child(startIndex)
90
89
  let inside = type.contentMatch.findWrapping(inner.type)
91
90
  if (!inside) return null
92
91
  let lastType = inside.length ? inside[inside.length - 1] : type
93
- let innerMatch = lastType.contentMatch
92
+ let innerMatch: ContentMatch | null = lastType.contentMatch
94
93
  for (let i = startIndex; innerMatch && i < endIndex; i++)
95
94
  innerMatch = innerMatch.matchType(parent.child(i).type)
96
95
  if (!innerMatch || !innerMatch.validEnd) return null
97
96
  return inside
98
97
  }
99
98
 
100
- // :: (NodeRange, [{type: NodeType, attrs: ?Object}]) → this
101
- // Wrap the given [range](#model.NodeRange) in the given set of wrappers.
102
- // The wrappers are assumed to be valid in this position, and should
103
- // probably be computed with [`findWrapping`](#transform.findWrapping).
104
- Transform.prototype.wrap = function(range, wrappers) {
99
+ export function wrap(tr: Transform, range: NodeRange, wrappers: readonly {type: NodeType, attrs?: Attrs | null}[]) {
105
100
  let content = Fragment.empty
106
101
  for (let i = wrappers.length - 1; i >= 0; i--) {
107
102
  if (content.size) {
@@ -113,55 +108,51 @@ Transform.prototype.wrap = function(range, wrappers) {
113
108
  }
114
109
 
115
110
  let start = range.start, end = range.end
116
- return this.step(new ReplaceAroundStep(start, end, start, end, new Slice(content, 0, 0), wrappers.length, true))
111
+ tr.step(new ReplaceAroundStep(start, end, start, end, new Slice(content, 0, 0), wrappers.length, true))
117
112
  }
118
113
 
119
- // :: (number, ?number, NodeType, ?Object) → this
120
- // Set the type of all textblocks (partly) between `from` and `to` to
121
- // the given node type with the given attributes.
122
- Transform.prototype.setBlockType = function(from, to = from, type, attrs) {
114
+ export function setBlockType(tr: Transform, from: number, to: number, type: NodeType, attrs: Attrs | null) {
123
115
  if (!type.isTextblock) throw new RangeError("Type given to setBlockType should be a textblock")
124
- let mapFrom = this.steps.length
125
- this.doc.nodesBetween(from, to, (node, pos) => {
126
- if (node.isTextblock && !node.hasMarkup(type, attrs) && canChangeType(this.doc, this.mapping.slice(mapFrom).map(pos), type)) {
116
+ let mapFrom = tr.steps.length
117
+ tr.doc.nodesBetween(from, to, (node, pos) => {
118
+ if (node.isTextblock && !node.hasMarkup(type, attrs) && canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
127
119
  // Ensure all markup that isn't allowed in the new node type is cleared
128
- this.clearIncompatible(this.mapping.slice(mapFrom).map(pos, 1), type)
129
- let mapping = this.mapping.slice(mapFrom)
120
+ tr.clearIncompatible(tr.mapping.slice(mapFrom).map(pos, 1), type)
121
+ let mapping = tr.mapping.slice(mapFrom)
130
122
  let startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1)
131
- this.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1,
123
+ tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1,
132
124
  new Slice(Fragment.from(type.create(attrs, null, node.marks)), 0, 0), 1, true))
133
125
  return false
134
126
  }
135
127
  })
136
- return this
137
128
  }
138
129
 
139
- function canChangeType(doc, pos, type) {
130
+ function canChangeType(doc: Node, pos: number, type: NodeType) {
140
131
  let $pos = doc.resolve(pos), index = $pos.index()
141
132
  return $pos.parent.canReplaceWith(index, index + 1, type)
142
133
  }
143
134
 
144
- // :: (number, ?NodeType, ?Object, ?[Mark]) → this
145
- // Change the type, attributes, and/or marks of the node at `pos`.
146
- // When `type` isn't given, the existing node type is preserved,
147
- Transform.prototype.setNodeMarkup = function(pos, type, attrs, marks) {
148
- let node = this.doc.nodeAt(pos)
135
+ /// Change the type, attributes, and/or marks of the node at `pos`.
136
+ /// When `type` isn't given, the existing node type is preserved,
137
+ export function setNodeMarkup(tr: Transform, pos: number, type: NodeType | undefined | null,
138
+ attrs: Attrs | null, marks: readonly Mark[]) {
139
+ let node = tr.doc.nodeAt(pos)
149
140
  if (!node) throw new RangeError("No node at given position")
150
141
  if (!type) type = node.type
151
142
  let newNode = type.create(attrs, null, marks || node.marks)
152
143
  if (node.isLeaf)
153
- return this.replaceWith(pos, pos + node.nodeSize, newNode)
144
+ return tr.replaceWith(pos, pos + node.nodeSize, newNode)
154
145
 
155
146
  if (!type.validContent(node.content))
156
147
  throw new RangeError("Invalid content for node type " + type.name)
157
148
 
158
- return this.step(new ReplaceAroundStep(pos, pos + node.nodeSize, pos + 1, pos + node.nodeSize - 1,
159
- new Slice(Fragment.from(newNode), 0, 0), 1, true))
149
+ tr.step(new ReplaceAroundStep(pos, pos + node.nodeSize, pos + 1, pos + node.nodeSize - 1,
150
+ new Slice(Fragment.from(newNode), 0, 0), 1, true))
160
151
  }
161
152
 
162
- // :: (Node, number, number, ?[?{type: NodeType, attrs: ?Object}]) → bool
163
- // Check whether splitting at the given position is allowed.
164
- export function canSplit(doc, pos, depth = 1, typesAfter) {
153
+ /// Check whether splitting at the given position is allowed.
154
+ export function canSplit(doc: Node, pos: number, depth = 1,
155
+ typesAfter?: (null | {type: NodeType, attrs?: Attrs | null})[]): boolean {
165
156
  let $pos = doc.resolve(pos), base = $pos.depth - depth
166
157
  let innerType = (typesAfter && typesAfter[typesAfter.length - 1]) || $pos.parent
167
158
  if (base < 0 || $pos.parent.type.spec.isolating ||
@@ -182,40 +173,32 @@ export function canSplit(doc, pos, depth = 1, typesAfter) {
182
173
  return $pos.node(base).canReplaceWith(index, index, baseType ? baseType.type : $pos.node(base + 1).type)
183
174
  }
184
175
 
185
- // :: (number, ?number, ?[?{type: NodeType, attrs: ?Object}]) → this
186
- // Split the node at the given position, and optionally, if `depth` is
187
- // greater than one, any number of nodes above that. By default, the
188
- // parts split off will inherit the node type of the original node.
189
- // This can be changed by passing an array of types and attributes to
190
- // use after the split.
191
- Transform.prototype.split = function(pos, depth = 1, typesAfter) {
192
- let $pos = this.doc.resolve(pos), before = Fragment.empty, after = Fragment.empty
176
+ export function split(tr: Transform, pos: number, depth = 1, typesAfter?: (null | {type: NodeType, attrs?: Attrs | null})[]) {
177
+ let $pos = tr.doc.resolve(pos), before = Fragment.empty, after = Fragment.empty
193
178
  for (let d = $pos.depth, e = $pos.depth - depth, i = depth - 1; d > e; d--, i--) {
194
179
  before = Fragment.from($pos.node(d).copy(before))
195
180
  let typeAfter = typesAfter && typesAfter[i]
196
181
  after = Fragment.from(typeAfter ? typeAfter.type.create(typeAfter.attrs, after) : $pos.node(d).copy(after))
197
182
  }
198
- return this.step(new ReplaceStep(pos, pos, new Slice(before.append(after), depth, depth), true))
183
+ tr.step(new ReplaceStep(pos, pos, new Slice(before.append(after), depth, depth), true))
199
184
  }
200
185
 
201
- // :: (Node, number) → bool
202
- // Test whether the blocks before and after a given position can be
203
- // joined.
204
- export function canJoin(doc, pos) {
186
+ /// Test whether the blocks before and after a given position can be
187
+ /// joined.
188
+ export function canJoin(doc: Node, pos: number): boolean {
205
189
  let $pos = doc.resolve(pos), index = $pos.index()
206
190
  return joinable($pos.nodeBefore, $pos.nodeAfter) &&
207
191
  $pos.parent.canReplace(index, index + 1)
208
192
  }
209
193
 
210
- function joinable(a, b) {
211
- return a && b && !a.isLeaf && a.canAppend(b)
194
+ function joinable(a: Node | null, b: Node | null) {
195
+ return !!(a && b && !a.isLeaf && a.canAppend(b))
212
196
  }
213
197
 
214
- // :: (Node, number, ?number) → ?number
215
- // Find an ancestor of the given position that can be joined to the
216
- // block before (or after if `dir` is positive). Returns the joinable
217
- // point, if any.
218
- export function joinPoint(doc, pos, dir = -1) {
198
+ /// Find an ancestor of the given position that can be joined to the
199
+ /// block before (or after if `dir` is positive). Returns the joinable
200
+ /// point, if any.
201
+ export function joinPoint(doc: Node, pos: number, dir = -1) {
219
202
  let $pos = doc.resolve(pos)
220
203
  for (let d = $pos.depth;; d--) {
221
204
  let before, after, index = $pos.index(d)
@@ -237,20 +220,16 @@ export function joinPoint(doc, pos, dir = -1) {
237
220
  }
238
221
  }
239
222
 
240
- // :: (number, ?number) → this
241
- // Join the blocks around the given position. If depth is 2, their
242
- // last and first siblings are also joined, and so on.
243
- Transform.prototype.join = function(pos, depth = 1) {
223
+ export function join(tr: Transform, pos: number, depth: number) {
244
224
  let step = new ReplaceStep(pos - depth, pos + depth, Slice.empty, true)
245
- return this.step(step)
225
+ tr.step(step)
246
226
  }
247
227
 
248
- // :: (Node, number, NodeType) → ?number
249
- // Try to find a point where a node of the given type can be inserted
250
- // near `pos`, by searching up the node hierarchy when `pos` itself
251
- // isn't a valid place but is at the start or end of a node. Return
252
- // null if no position was found.
253
- export function insertPoint(doc, pos, nodeType) {
228
+ /// Try to find a point where a node of the given type can be inserted
229
+ /// near `pos`, by searching up the node hierarchy when `pos` itself
230
+ /// isn't a valid place but is at the start or end of a node. Return
231
+ /// null if no position was found.
232
+ export function insertPoint(doc: Node, pos: number, nodeType: NodeType): number | null {
254
233
  let $pos = doc.resolve(pos)
255
234
  if ($pos.parent.canReplaceWith($pos.index(), $pos.index(), nodeType)) return pos
256
235
 
@@ -266,27 +245,27 @@ export function insertPoint(doc, pos, nodeType) {
266
245
  if ($pos.node(d).canReplaceWith(index, index, nodeType)) return $pos.after(d + 1)
267
246
  if (index < $pos.node(d).childCount) return null
268
247
  }
248
+ return null
269
249
  }
270
250
 
271
- // :: (Node, number, Slice) → ?number
272
- // Finds a position at or around the given position where the given
273
- // slice can be inserted. Will look at parent nodes' nearest boundary
274
- // and try there, even if the original position wasn't directly at the
275
- // start or end of that node. Returns null when no position was found.
276
- export function dropPoint(doc, pos, slice) {
251
+ /// Finds a position at or around the given position where the given
252
+ /// slice can be inserted. Will look at parent nodes' nearest boundary
253
+ /// and try there, even if the original position wasn't directly at the
254
+ /// start or end of that node. Returns null when no position was found.
255
+ export function dropPoint(doc: Node, pos: number, slice: Slice): number | null {
277
256
  let $pos = doc.resolve(pos)
278
257
  if (!slice.content.size) return pos
279
258
  let content = slice.content
280
- for (let i = 0; i < slice.openStart; i++) content = content.firstChild.content
259
+ for (let i = 0; i < slice.openStart; i++) content = content.firstChild!.content
281
260
  for (let pass = 1; pass <= (slice.openStart == 0 && slice.size ? 2 : 1); pass++) {
282
261
  for (let d = $pos.depth; d >= 0; d--) {
283
262
  let bias = d == $pos.depth ? 0 : $pos.pos <= ($pos.start(d + 1) + $pos.end(d + 1)) / 2 ? -1 : 1
284
263
  let insertPos = $pos.index(d) + (bias > 0 ? 1 : 0)
285
- let parent = $pos.node(d), fits = false
264
+ let parent = $pos.node(d), fits: boolean | null = false
286
265
  if (pass == 1) {
287
266
  fits = parent.canReplace(insertPos, insertPos, content)
288
267
  } else {
289
- let wrapping = parent.contentMatchAt(insertPos).findWrapping(content.firstChild.type)
268
+ let wrapping = parent.contentMatchAt(insertPos).findWrapping(content.firstChild!.type)
290
269
  fits = wrapping && parent.canReplaceWith(insertPos, insertPos, wrapping[0])
291
270
  }
292
271
  if (fits)