prosemirror-transform 1.9.0 → 1.10.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 1.10.1 (2024-10-10)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where a `deleteRange` heuristic could produce unexpected deletion shapes.
6
+
7
+ Make `Transform.join` convert between newlines and line break replacement nodes when necessary.
8
+
9
+ ## 1.10.0 (2024-08-13)
10
+
11
+ ### New features
12
+
13
+ `setBlockType` can now take a function that computes attributes for the new nodes, instead of a static attribute object.
14
+
1
15
  ## 1.9.0 (2024-05-06)
2
16
 
3
17
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -942,7 +942,8 @@ function _setBlockType(tr, from, to, type, attrs) {
942
942
  if (!type.isTextblock) throw new RangeError("Type given to setBlockType should be a textblock");
943
943
  var mapFrom = tr.steps.length;
944
944
  tr.doc.nodesBetween(from, to, function (node, pos) {
945
- if (node.isTextblock && !node.hasMarkup(type, attrs) && canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
945
+ var attrsHere = typeof attrs == "function" ? attrs(node) : attrs;
946
+ if (node.isTextblock && !node.hasMarkup(type, attrsHere) && canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
946
947
  var convertNewlines = null;
947
948
  if (type.schema.linebreakReplacement) {
948
949
  var pre = type.whitespace == "pre",
@@ -954,7 +955,7 @@ function _setBlockType(tr, from, to, type, attrs) {
954
955
  var mapping = tr.mapping.slice(mapFrom);
955
956
  var startM = mapping.map(pos, 1),
956
957
  endM = mapping.map(pos + node.nodeSize, 1);
957
- 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));
958
+ tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1, new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(type.create(attrsHere, null, node.marks)), 0, 0), 1, true));
958
959
  if (convertNewlines === true) replaceNewlines(tr, node, pos, mapFrom);
959
960
  return false;
960
961
  }
@@ -1033,8 +1034,21 @@ function canJoin(doc, pos) {
1033
1034
  index = $pos.index();
1034
1035
  return joinable($pos.nodeBefore, $pos.nodeAfter) && $pos.parent.canReplace(index, index + 1);
1035
1036
  }
1037
+ function canAppendWithSubstitutedLinebeaks(a, b) {
1038
+ if (!b.content.size) a.type.compatibleContent(b.type);
1039
+ var match = a.contentMatchAt(a.childCount);
1040
+ var linebreakReplacement = a.type.schema.linebreakReplacement;
1041
+ for (var i = 0; i < b.childCount; i++) {
1042
+ var child = b.child(i);
1043
+ var type = child.type == linebreakReplacement ? a.type.schema.nodes.text : child.type;
1044
+ match = match.matchType(type);
1045
+ if (!match) return false;
1046
+ if (!a.type.allowsMarks(child.marks)) return false;
1047
+ }
1048
+ return match.validEnd;
1049
+ }
1036
1050
  function joinable(a, b) {
1037
- return !!(a && b && !a.isLeaf && a.canAppend(b));
1051
+ return !!(a && b && !a.isLeaf && canAppendWithSubstitutedLinebeaks(a, b));
1038
1052
  }
1039
1053
  function joinPoint(doc, pos) {
1040
1054
  var dir = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : -1;
@@ -1060,8 +1074,27 @@ function joinPoint(doc, pos) {
1060
1074
  }
1061
1075
  }
1062
1076
  function _join(tr, pos, depth) {
1063
- var step = new ReplaceStep(pos - depth, pos + depth, prosemirrorModel.Slice.empty, true);
1064
- tr.step(step);
1077
+ var convertNewlines = null;
1078
+ var linebreakReplacement = tr.doc.type.schema.linebreakReplacement;
1079
+ if (linebreakReplacement) {
1080
+ var before = tr.doc.resolve(pos - depth).node().type;
1081
+ var pre = before.whitespace == "pre";
1082
+ var supportLinebreak = !!before.contentMatch.matchType(linebreakReplacement);
1083
+ if (pre && !supportLinebreak) convertNewlines = false;else if (!pre && supportLinebreak) convertNewlines = true;
1084
+ }
1085
+ var mapFrom = tr.steps.length;
1086
+ if (convertNewlines === false) {
1087
+ var $after = tr.doc.resolve(pos + depth);
1088
+ replaceLinebreaks(tr, $after.node(), $after.before(), mapFrom);
1089
+ }
1090
+ var mapping = tr.mapping.slice(mapFrom),
1091
+ start = mapping.map(pos - depth);
1092
+ tr.step(new ReplaceStep(start, mapping.map(pos + depth, -1), prosemirrorModel.Slice.empty, true));
1093
+ if (convertNewlines === true) {
1094
+ var $full = tr.doc.resolve(start);
1095
+ replaceNewlines(tr, $full.node(), $full.before(), tr.steps.length);
1096
+ }
1097
+ return tr;
1065
1098
  }
1066
1099
  function insertPoint(doc, pos, nodeType) {
1067
1100
  var $pos = doc.resolve(pos);
@@ -1474,7 +1507,7 @@ function _deleteRange(tr, from, to) {
1474
1507
  if (depth > 0 && (last || $from.node(depth - 1).canReplace($from.index(depth - 1), $to.indexAfter(depth - 1)))) return tr["delete"]($from.before(depth), $to.after(depth));
1475
1508
  }
1476
1509
  for (var d = 1; d <= $from.depth && d <= $to.depth; d++) {
1477
- if (from - $from.start(d) == $from.depth - d && to > $from.end(d) && $to.end(d) - to != $to.depth - d) return tr["delete"]($from.before(d), to);
1510
+ if (from - $from.start(d) == $from.depth - d && to > $from.end(d) && $to.end(d) - to != $to.depth - d && $from.start(d - 1) == $to.start(d - 1) && $from.node(d - 1).canReplace($from.index(d - 1), $to.index(d - 1))) return tr["delete"]($from.before(d), to);
1478
1511
  }
1479
1512
  tr["delete"](from, to);
1480
1513
  }
package/dist/index.d.cts CHANGED
@@ -406,7 +406,7 @@ declare class Transform {
406
406
  Set the type of all textblocks (partly) between `from` and `to` to
407
407
  the given node type with the given attributes.
408
408
  */
409
- setBlockType(from: number, to: number | undefined, type: NodeType, attrs?: Attrs | null): this;
409
+ setBlockType(from: number, to: number | undefined, type: NodeType, attrs?: Attrs | null | ((oldNode: Node) => Attrs)): this;
410
410
  /**
411
411
  Change the type, attributes, and/or marks of the node at `pos`.
412
412
  When `type` isn't given, the existing node type is preserved,
package/dist/index.d.ts CHANGED
@@ -406,7 +406,7 @@ declare class Transform {
406
406
  Set the type of all textblocks (partly) between `from` and `to` to
407
407
  the given node type with the given attributes.
408
408
  */
409
- setBlockType(from: number, to: number | undefined, type: NodeType, attrs?: Attrs | null): this;
409
+ setBlockType(from: number, to: number | undefined, type: NodeType, attrs?: Attrs | null | ((oldNode: Node) => Attrs)): this;
410
410
  /**
411
411
  Change the type, attributes, and/or marks of the node at `pos`.
412
412
  When `type` isn't given, the existing node type is preserved,
package/dist/index.js CHANGED
@@ -1075,7 +1075,9 @@ function setBlockType(tr, from, to, type, attrs) {
1075
1075
  throw new RangeError("Type given to setBlockType should be a textblock");
1076
1076
  let mapFrom = tr.steps.length;
1077
1077
  tr.doc.nodesBetween(from, to, (node, pos) => {
1078
- if (node.isTextblock && !node.hasMarkup(type, attrs) && canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
1078
+ let attrsHere = typeof attrs == "function" ? attrs(node) : attrs;
1079
+ if (node.isTextblock && !node.hasMarkup(type, attrsHere) &&
1080
+ canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
1079
1081
  let convertNewlines = null;
1080
1082
  if (type.schema.linebreakReplacement) {
1081
1083
  let pre = type.whitespace == "pre", supportLinebreak = !!type.contentMatch.matchType(type.schema.linebreakReplacement);
@@ -1090,7 +1092,7 @@ function setBlockType(tr, from, to, type, attrs) {
1090
1092
  clearIncompatible(tr, tr.mapping.slice(mapFrom).map(pos, 1), type, undefined, convertNewlines === null);
1091
1093
  let mapping = tr.mapping.slice(mapFrom);
1092
1094
  let startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1);
1093
- tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1, new Slice(Fragment.from(type.create(attrs, null, node.marks)), 0, 0), 1, true));
1095
+ tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1, new Slice(Fragment.from(type.create(attrsHere, null, node.marks)), 0, 0), 1, true));
1094
1096
  if (convertNewlines === true)
1095
1097
  replaceNewlines(tr, node, pos, mapFrom);
1096
1098
  return false;
@@ -1181,8 +1183,24 @@ function canJoin(doc, pos) {
1181
1183
  return joinable($pos.nodeBefore, $pos.nodeAfter) &&
1182
1184
  $pos.parent.canReplace(index, index + 1);
1183
1185
  }
1186
+ function canAppendWithSubstitutedLinebeaks(a, b) {
1187
+ if (!b.content.size)
1188
+ a.type.compatibleContent(b.type);
1189
+ let match = a.contentMatchAt(a.childCount);
1190
+ let { linebreakReplacement } = a.type.schema;
1191
+ for (let i = 0; i < b.childCount; i++) {
1192
+ let child = b.child(i);
1193
+ let type = child.type == linebreakReplacement ? a.type.schema.nodes.text : child.type;
1194
+ match = match.matchType(type);
1195
+ if (!match)
1196
+ return false;
1197
+ if (!a.type.allowsMarks(child.marks))
1198
+ return false;
1199
+ }
1200
+ return match.validEnd;
1201
+ }
1184
1202
  function joinable(a, b) {
1185
- return !!(a && b && !a.isLeaf && a.canAppend(b));
1203
+ return !!(a && b && !a.isLeaf && canAppendWithSubstitutedLinebeaks(a, b));
1186
1204
  }
1187
1205
  /**
1188
1206
  Find an ancestor of the given position that can be joined to the
@@ -1215,8 +1233,29 @@ function joinPoint(doc, pos, dir = -1) {
1215
1233
  }
1216
1234
  }
1217
1235
  function join(tr, pos, depth) {
1218
- let step = new ReplaceStep(pos - depth, pos + depth, Slice.empty, true);
1219
- tr.step(step);
1236
+ let convertNewlines = null;
1237
+ let { linebreakReplacement } = tr.doc.type.schema;
1238
+ if (linebreakReplacement) {
1239
+ let before = tr.doc.resolve(pos - depth).node().type;
1240
+ let pre = before.whitespace == "pre";
1241
+ let supportLinebreak = !!before.contentMatch.matchType(linebreakReplacement);
1242
+ if (pre && !supportLinebreak)
1243
+ convertNewlines = false;
1244
+ else if (!pre && supportLinebreak)
1245
+ convertNewlines = true;
1246
+ }
1247
+ let mapFrom = tr.steps.length;
1248
+ if (convertNewlines === false) {
1249
+ let $after = tr.doc.resolve(pos + depth);
1250
+ replaceLinebreaks(tr, $after.node(), $after.before(), mapFrom);
1251
+ }
1252
+ let mapping = tr.mapping.slice(mapFrom), start = mapping.map(pos - depth);
1253
+ tr.step(new ReplaceStep(start, mapping.map(pos + depth, -1), Slice.empty, true));
1254
+ if (convertNewlines === true) {
1255
+ let $full = tr.doc.resolve(start);
1256
+ replaceNewlines(tr, $full.node(), $full.before(), tr.steps.length);
1257
+ }
1258
+ return tr;
1220
1259
  }
1221
1260
  /**
1222
1261
  Try to find a point where a node of the given type can be inserted
@@ -1700,7 +1739,8 @@ function deleteRange(tr, from, to) {
1700
1739
  return tr.delete($from.before(depth), $to.after(depth));
1701
1740
  }
1702
1741
  for (let d = 1; d <= $from.depth && d <= $to.depth; d++) {
1703
- if (from - $from.start(d) == $from.depth - d && to > $from.end(d) && $to.end(d) - to != $to.depth - d)
1742
+ if (from - $from.start(d) == $from.depth - d && to > $from.end(d) && $to.end(d) - to != $to.depth - d &&
1743
+ $from.start(d - 1) == $to.start(d - 1) && $from.node(d - 1).canReplace($from.index(d - 1), $to.index(d - 1)))
1704
1744
  return tr.delete($from.before(d), to);
1705
1745
  }
1706
1746
  tr.delete(from, to);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-transform",
3
- "version": "1.9.0",
3
+ "version": "1.10.1",
4
4
  "description": "ProseMirror document transformations",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
package/src/replace.ts CHANGED
@@ -434,7 +434,8 @@ export function deleteRange(tr: Transform, from: number, to: number) {
434
434
  return tr.delete($from.before(depth), $to.after(depth))
435
435
  }
436
436
  for (let d = 1; d <= $from.depth && d <= $to.depth; d++) {
437
- if (from - $from.start(d) == $from.depth - d && to > $from.end(d) && $to.end(d) - to != $to.depth - d)
437
+ if (from - $from.start(d) == $from.depth - d && to > $from.end(d) && $to.end(d) - to != $to.depth - d &&
438
+ $from.start(d - 1) == $to.start(d - 1) && $from.node(d - 1).canReplace($from.index(d - 1), $to.index(d - 1)))
438
439
  return tr.delete($from.before(d), to)
439
440
  }
440
441
  tr.delete(from, to)
package/src/structure.ts CHANGED
@@ -112,11 +112,14 @@ export function wrap(tr: Transform, range: NodeRange, wrappers: readonly {type:
112
112
  tr.step(new ReplaceAroundStep(start, end, start, end, new Slice(content, 0, 0), wrappers.length, true))
113
113
  }
114
114
 
115
- export function setBlockType(tr: Transform, from: number, to: number, type: NodeType, attrs: Attrs | null) {
115
+ export function setBlockType(tr: Transform, from: number, to: number,
116
+ type: NodeType, attrs: Attrs | null | ((oldNode: Node) => Attrs)) {
116
117
  if (!type.isTextblock) throw new RangeError("Type given to setBlockType should be a textblock")
117
118
  let mapFrom = tr.steps.length
118
119
  tr.doc.nodesBetween(from, to, (node, pos) => {
119
- if (node.isTextblock && !node.hasMarkup(type, attrs) && canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
120
+ let attrsHere = typeof attrs == "function" ? attrs(node) : attrs
121
+ if (node.isTextblock && !node.hasMarkup(type, attrsHere) &&
122
+ canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
120
123
  let convertNewlines = null
121
124
  if (type.schema.linebreakReplacement) {
122
125
  let pre = type.whitespace == "pre", supportLinebreak = !!type.contentMatch.matchType(type.schema.linebreakReplacement)
@@ -129,7 +132,7 @@ export function setBlockType(tr: Transform, from: number, to: number, type: Node
129
132
  let mapping = tr.mapping.slice(mapFrom)
130
133
  let startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1)
131
134
  tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1,
132
- new Slice(Fragment.from(type.create(attrs, null, node.marks)), 0, 0), 1, true))
135
+ new Slice(Fragment.from(type.create(attrsHere, null, node.marks)), 0, 0), 1, true))
133
136
  if (convertNewlines === true) replaceNewlines(tr, node, pos, mapFrom)
134
137
  return false
135
138
  }
@@ -223,8 +226,22 @@ export function canJoin(doc: Node, pos: number): boolean {
223
226
  $pos.parent.canReplace(index, index + 1)
224
227
  }
225
228
 
229
+ function canAppendWithSubstitutedLinebeaks(a: Node, b: Node) {
230
+ if (!b.content.size) a.type.compatibleContent(b.type)
231
+ let match: ContentMatch | null = a.contentMatchAt(a.childCount)
232
+ let {linebreakReplacement} = a.type.schema
233
+ for (let i = 0; i < b.childCount; i++) {
234
+ let child = b.child(i)
235
+ let type = child.type == linebreakReplacement ? a.type.schema.nodes.text : child.type
236
+ match = match.matchType(type)
237
+ if (!match) return false
238
+ if (!a.type.allowsMarks(child.marks)) return false
239
+ }
240
+ return match.validEnd
241
+ }
242
+
226
243
  function joinable(a: Node | null, b: Node | null) {
227
- return !!(a && b && !a.isLeaf && a.canAppend(b))
244
+ return !!(a && b && !a.isLeaf && canAppendWithSubstitutedLinebeaks(a, b))
228
245
  }
229
246
 
230
247
  /// Find an ancestor of the given position that can be joined to the
@@ -253,8 +270,27 @@ export function joinPoint(doc: Node, pos: number, dir = -1) {
253
270
  }
254
271
 
255
272
  export function join(tr: Transform, pos: number, depth: number) {
256
- let step = new ReplaceStep(pos - depth, pos + depth, Slice.empty, true)
257
- tr.step(step)
273
+ let convertNewlines = null
274
+ let {linebreakReplacement} = tr.doc.type.schema
275
+ if (linebreakReplacement) {
276
+ let before = tr.doc.resolve(pos - depth).node().type
277
+ let pre = before.whitespace == "pre"
278
+ let supportLinebreak = !!before.contentMatch.matchType(linebreakReplacement)
279
+ if (pre && !supportLinebreak) convertNewlines = false
280
+ else if (!pre && supportLinebreak) convertNewlines = true
281
+ }
282
+ let mapFrom = tr.steps.length
283
+ if (convertNewlines === false) {
284
+ let $after = tr.doc.resolve(pos + depth)
285
+ replaceLinebreaks(tr, $after.node(), $after.before(), mapFrom)
286
+ }
287
+ let mapping = tr.mapping.slice(mapFrom), start = mapping.map(pos - depth)
288
+ tr.step(new ReplaceStep(start, mapping.map(pos + depth, - 1), Slice.empty, true))
289
+ if (convertNewlines === true) {
290
+ let $full = tr.doc.resolve(start)
291
+ replaceNewlines(tr, $full.node(), $full.before(), tr.steps.length)
292
+ }
293
+ return tr
258
294
  }
259
295
 
260
296
  /// Try to find a point where a node of the given type can be inserted
package/src/transform.ts CHANGED
@@ -166,7 +166,7 @@ export class Transform {
166
166
 
167
167
  /// Set the type of all textblocks (partly) between `from` and `to` to
168
168
  /// the given node type with the given attributes.
169
- setBlockType(from: number, to = from, type: NodeType, attrs: Attrs | null = null): this {
169
+ setBlockType(from: number, to = from, type: NodeType, attrs: Attrs | null | ((oldNode: Node) => Attrs) = null): this {
170
170
  setBlockType(this, from, to, type, attrs)
171
171
  return this
172
172
  }