vendor: 4,367 bytes, lines 1-122
1// [email protected] downloaded from https://cdn.jsdelivr.net/npm/[email protected]/dist/index.js 2 3import { ReplaceError, Slice, Fragment, MarkType, Mark } from 'prosemirror-model'; 4 5// Recovery values encode a range index and an offset. They are 6// represented as numbers, because tons of them will be created when 7// mapping, for example, a large number of decorations. The number's 8// lower 16 bits provide the index, the remaining bits the offset. 9// 10// Note: We intentionally don't use bit shift operators to en- and 11// decode these, since those clip to 32 bits, which we might in rare 12// cases want to overflow. A 64-bit float can represent 48-bit 13// integers precisely. 14const lower16 = 0xffff; 15const factor16 = Math.pow(2, 16); 16function makeRecover(index, offset) { return index + offset * factor16; } 17function recoverIndex(value) { return value & lower16; } 18function recoverOffset(value) { return (value - (value & lower16)) / factor16; } 19const DEL_BEFORE = 1, DEL_AFTER = 2, DEL_ACROSS = 4, DEL_SIDE = 8; 20/** 21An object representing a mapped position with extra 22information. 23*/ 24class MapResult { 25 /** 26 @internal 27 */ 28 constructor( 29 /** 30 The mapped version of the position. 31 */ 32 pos, 33 /** 34 @internal 35 */ 36 delInfo, 37 /** 38 @internal 39 */ 40 recover) { 41 this.pos = pos; 42 this.delInfo = delInfo; 43 this.recover = recover; 44 } 45 /** 46 Tells you whether the position was deleted, that is, whether the 47 step removed the token on the side queried (via the `assoc`) 48 argument from the document. 49 */ 50 get deleted() { return (this.delInfo & DEL_SIDE) > 0; } 51 /** 52 Tells you whether the token before the mapped position was deleted. 53 */ 54 get deletedBefore() { return (this.delInfo & (DEL_BEFORE | DEL_ACROSS)) > 0; } 55 /** 56 True when the token after the mapped position was deleted. 57 */ 58 get deletedAfter() { return (this.delInfo & (DEL_AFTER | DEL_ACROSS)) > 0; } 59 /** 60 Tells whether any of the steps mapped through deletes across the 61 position (including both the token before and after the 62 position). 63 */ 64 get deletedAcross() { return (this.delInfo & DEL_ACROSS) > 0; } 65} 66/** 67A map describing the deletions and insertions made by a step, which 68can be used to find the correspondence between positions in the 69pre-step version of a document and the same position in the 70post-step version. 71*/ 72class StepMap { 73 /** 74 Create a position map. The modifications to the document are 75 represented as an array of numbers, in which each group of three 76 represents a modified chunk as `[start, oldSize, newSize]`. 77 */ 78 constructor( 79 /** 80 @internal 81 */ 82 ranges, 83 /** 84 @internal 85 */ 86 inverted = false) { 87 this.ranges = ranges; 88 this.inverted = inverted; 89 if (!ranges.length && StepMap.empty) 90 return StepMap.empty; 91 } 92 /** 93 @internal 94 */ 95 recover(value) { 96 let diff = 0, index = recoverIndex(value); 97 if (!this.inverted) 98 for (let i = 0; i < index; i++) 99 diff += this.ranges[i * 3 + 2] - this.ranges[i * 3 + 1]; 100 return this.ranges[index * 3] + diff + recoverOffset(value); 101 } 102 mapResult(pos, assoc = 1) { return this._map(pos, assoc, false); } 103 map(pos, assoc = 1) { return this._map(pos, assoc, true); } 104 /** 105 @internal 106 */ 107 _map(pos, assoc, simple) { 108 let diff = 0, oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2; 109 for (let i = 0; i < this.ranges.length; i += 3) { 110 let start = this.ranges[i] - (this.inverted ? diff : 0); 111 if (start > pos) 112 break; 113 let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex], end = start + oldSize; 114 if (pos <= end) { 115 let side = !oldSize ? assoc : pos == start ? -1 : pos == end ? 1 : assoc; 116 let result = start + diff + (side < 0 ? 0 : newSize); 117 if (simple) 118 return result; 119 let recover = pos == (assoc < 0 ? start : end) ? null : makeRecover(i / 3, pos - start); 120 let del = pos == start ? DEL_AFTER : pos == end ? DEL_BEFORE : DEL_ACROSS; 121 if (assoc < 0 ? pos != start : pos != end) 122 del |= DEL_SIDE;
vendor: 42,719 bytes, lines 122-1290
122 123 return new MapResult(result, del, recover); 124 } 125 diff += newSize - oldSize; 126 } 127 return simple ? pos + diff : new MapResult(pos + diff, 0, null); 128 } 129 /** 130 @internal 131 */ 132 touches(pos, recover) { 133 let diff = 0, index = recoverIndex(recover); 134 let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2; 135 for (let i = 0; i < this.ranges.length; i += 3) { 136 let start = this.ranges[i] - (this.inverted ? diff : 0); 137 if (start > pos) 138 break; 139 let oldSize = this.ranges[i + oldIndex], end = start + oldSize; 140 if (pos <= end && i == index * 3) 141 return true; 142 diff += this.ranges[i + newIndex] - oldSize; 143 } 144 return false; 145 } 146 /** 147 Calls the given function on each of the changed ranges included in 148 this map. 149 */ 150 forEach(f) { 151 let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2; 152 for (let i = 0, diff = 0; i < this.ranges.length; i += 3) { 153 let start = this.ranges[i], oldStart = start - (this.inverted ? diff : 0), newStart = start + (this.inverted ? 0 : diff); 154 let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex]; 155 f(oldStart, oldStart + oldSize, newStart, newStart + newSize); 156 diff += newSize - oldSize; 157 } 158 } 159 /** 160 Create an inverted version of this map. The result can be used to 161 map positions in the post-step document to the pre-step document. 162 */ 163 invert() { 164 return new StepMap(this.ranges, !this.inverted); 165 } 166 /** 167 @internal 168 */ 169 toString() { 170 return (this.inverted ? "-" : "") + JSON.stringify(this.ranges); 171 } 172 /** 173 Create a map that moves all positions by offset `n` (which may be 174 negative). This can be useful when applying steps meant for a 175 sub-document to a larger document, or vice-versa. 176 */ 177 static offset(n) { 178 return n == 0 ? StepMap.empty : new StepMap(n < 0 ? [0, -n, 0] : [0, 0, n]); 179 } 180} 181/** 182A StepMap that contains no changed ranges. 183*/ 184StepMap.empty = new StepMap([]); 185/** 186A mapping represents a pipeline of zero or more [step 187maps](https://prosemirror.net/docs/ref/#transform.StepMap). It has special provisions for losslessly 188handling mapping positions through a series of steps in which some 189steps are inverted versions of earlier steps. (This comes up when 190â[rebasing](https://prosemirror.net/docs/guide/#transform.rebasing)â steps for 191collaboration or history management.) 192*/ 193class Mapping { 194 /** 195 Create a new mapping with the given position maps. 196 */ 197 constructor(maps, 198 /** 199 @internal 200 */ 201 mirror, 202 /** 203 The starting position in the `maps` array, used when `map` or 204 `mapResult` is called. 205 */ 206 from = 0, 207 /** 208 The end position in the `maps` array. 209 */ 210 to = maps ? maps.length : 0) { 211 this.mirror = mirror; 212 this.from = from; 213 this.to = to; 214 this._maps = maps || []; 215 this.ownData = !(maps || mirror); 216 } 217 /** 218 The step maps in this mapping. 219 */ 220 get maps() { return this._maps; } 221 /** 222 Create a mapping that maps only through a part of this one. 223 */ 224 slice(from = 0, to = this.maps.length) { 225 return new Mapping(this._maps, this.mirror, from, to); 226 } 227 /** 228 Add a step map to the end of this mapping. If `mirrors` is 229 given, it should be the index of the step map that is the mirror 230 image of this one. 231 */ 232 appendMap(map, mirrors) { 233 if (!this.ownData) { 234 this._maps = this._maps.slice(); 235 this.mirror = this.mirror && this.mirror.slice(); 236 this.ownData = true; 237 } 238 this.to = this._maps.push(map); 239 if (mirrors != null) 240 this.setMirror(this._maps.length - 1, mirrors); 241 } 242 /** 243 Add all the step maps in a given mapping to this one (preserving 244 mirroring information). 245 */ 246 appendMapping(mapping) { 247 for (let i = 0, startSize = this._maps.length; i < mapping._maps.length; i++) { 248 let mirr = mapping.getMirror(i); 249 this.appendMap(mapping._maps[i], mirr != null && mirr < i ? startSize + mirr : undefined); 250 } 251 } 252 /** 253 Finds the offset of the step map that mirrors the map at the 254 given offset, in this mapping (as per the second argument to 255 `appendMap`). 256 */ 257 getMirror(n) { 258 if (this.mirror) 259 for (let i = 0; i < this.mirror.length; i++) 260 if (this.mirror[i] == n) 261 return this.mirror[i + (i % 2 ? -1 : 1)]; 262 } 263 /** 264 @internal 265 */ 266 setMirror(n, m) { 267 if (!this.mirror) 268 this.mirror = []; 269 this.mirror.push(n, m); 270 } 271 /** 272 Append the inverse of the given mapping to this one. 273 */ 274 appendMappingInverted(mapping) { 275 for (let i = mapping.maps.length - 1, totalSize = this._maps.length + mapping._maps.length; i >= 0; i--) { 276 let mirr = mapping.getMirror(i); 277 this.appendMap(mapping._maps[i].invert(), mirr != null && mirr > i ? totalSize - mirr - 1 : undefined); 278 } 279 } 280 /** 281 Create an inverted version of this mapping. 282 */ 283 invert() { 284 let inverse = new Mapping; 285 inverse.appendMappingInverted(this); 286 return inverse; 287 } 288 /** 289 Map a position through this mapping. 290 */ 291 map(pos, assoc = 1) { 292 if (this.mirror) 293 return this._map(pos, assoc, true); 294 for (let i = this.from; i < this.to; i++) 295 pos = this._maps[i].map(pos, assoc); 296 return pos; 297 } 298 /** 299 Map a position through this mapping, returning a mapping 300 result. 301 */ 302 mapResult(pos, assoc = 1) { return this._map(pos, assoc, false); } 303 /** 304 @internal 305 */ 306 _map(pos, assoc, simple) { 307 let delInfo = 0; 308 for (let i = this.from; i < this.to; i++) { 309 let map = this._maps[i], result = map.mapResult(pos, assoc); 310 if (result.recover != null) { 311 let corr = this.getMirror(i); 312 if (corr != null && corr > i && corr < this.to) { 313 i = corr; 314 pos = this._maps[corr].recover(result.recover); 315 continue; 316 } 317 } 318 delInfo |= result.delInfo; 319 pos = result.pos; 320 } 321 return simple ? pos : new MapResult(pos, delInfo, null); 322 } 323} 324 325const stepsByID = Object.create(null); 326/** 327A step object represents an atomic change. It generally applies 328only to the document it was created for, since the positions 329stored in it will only make sense for that document. 330 331New steps are defined by creating classes that extend `Step`, 332overriding the `apply`, `invert`, `map`, `getMap` and `fromJSON` 333methods, and registering your class with a unique 334JSON-serialization identifier using 335[`Step.jsonID`](https://prosemirror.net/docs/ref/#transform.Step^jsonID). 336*/ 337class Step { 338 /** 339 Get the step map that represents the changes made by this step, 340 and which can be used to transform between positions in the old 341 and the new document. 342 */ 343 getMap() { return StepMap.empty; } 344 /** 345 Try to merge this step with another one, to be applied directly 346 after it. Returns the merged step when possible, null if the 347 steps can't be merged. 348 */ 349 merge(other) { return null; } 350 /** 351 Deserialize a step from its JSON representation. Will call 352 through to the step class' own implementation of this method. 353 */ 354 static fromJSON(schema, json) { 355 if (!json || !json.stepType) 356 throw new RangeError("Invalid input for Step.fromJSON"); 357 let type = stepsByID[json.stepType]; 358 if (!type) 359 throw new RangeError(`No step type ${json.stepType} defined`); 360 return type.fromJSON(schema, json); 361 } 362 /** 363 To be able to serialize steps to JSON, each step needs a string 364 ID to attach to its JSON representation. Use this method to 365 register an ID for your step classes. Try to pick something 366 that's unlikely to clash with steps from other modules. 367 */ 368 static jsonID(id, stepClass) { 369 if (id in stepsByID) 370 throw new RangeError("Duplicate use of step JSON ID " + id); 371 stepsByID[id] = stepClass; 372 stepClass.prototype.jsonID = id; 373 return stepClass; 374 } 375} 376/** 377The result of [applying](https://prosemirror.net/docs/ref/#transform.Step.apply) a step. Contains either a 378new document or a failure value. 379*/ 380class StepResult { 381 /** 382 @internal 383 */ 384 constructor( 385 /** 386 The transformed document, if successful. 387 */ 388 doc, 389 /** 390 The failure message, if unsuccessful. 391 */ 392 failed) { 393 this.doc = doc; 394 this.failed = failed; 395 } 396 /** 397 Create a successful step result. 398 */ 399 static ok(doc) { return new StepResult(doc, null); } 400 /** 401 Create a failed step result. 402 */ 403 static fail(message) { return new StepResult(null, message); } 404 /** 405 Call [`Node.replace`](https://prosemirror.net/docs/ref/#model.Node.replace) with the given 406 arguments. Create a successful result if it succeeds, and a 407 failed one if it throws a `ReplaceError`. 408 */ 409 static fromReplace(doc, from, to, slice) { 410 try { 411 return StepResult.ok(doc.replace(from, to, slice)); 412 } 413 catch (e) { 414 if (e instanceof ReplaceError) 415 return StepResult.fail(e.message); 416 throw e; 417 } 418 } 419} 420 421function mapFragment(fragment, f, parent) { 422 let mapped = []; 423 for (let i = 0; i < fragment.childCount; i++) { 424 let child = fragment.child(i); 425 if (child.content.size) 426 child = child.copy(mapFragment(child.content, f, child)); 427 if (child.isInline) 428 child = f(child, parent, i); 429 mapped.push(child); 430 } 431 return Fragment.fromArray(mapped); 432} 433/** 434Add a mark to all inline content between two positions. 435*/ 436class AddMarkStep extends Step { 437 /** 438 Create a mark step. 439 */ 440 constructor( 441 /** 442 The start of the marked range. 443 */ 444 from, 445 /** 446 The end of the marked range. 447 */ 448 to, 449 /** 450 The mark to add. 451 */ 452 mark) { 453 super(); 454 this.from = from; 455 this.to = to; 456 this.mark = mark; 457 } 458 apply(doc) { 459 let oldSlice = doc.slice(this.from, this.to), $from = doc.resolve(this.from); 460 let parent = $from.node($from.sharedDepth(this.to)); 461 let slice = new Slice(mapFragment(oldSlice.content, (node, parent) => { 462 if (!node.isAtom || !parent.type.allowsMarkType(this.mark.type)) 463 return node; 464 return node.mark(this.mark.addToSet(node.marks)); 465 }, parent), oldSlice.openStart, oldSlice.openEnd); 466 return StepResult.fromReplace(doc, this.from, this.to, slice); 467 } 468 invert() { 469 return new RemoveMarkStep(this.from, this.to, this.mark); 470 } 471 map(mapping) { 472 let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1); 473 if (from.deleted && to.deleted || from.pos >= to.pos) 474 return null; 475 return new AddMarkStep(from.pos, to.pos, this.mark); 476 } 477 merge(other) { 478 if (other instanceof AddMarkStep && 479 other.mark.eq(this.mark) && 480 this.from <= other.to && this.to >= other.from) 481 return new AddMarkStep(Math.min(this.from, other.from), Math.max(this.to, other.to), this.mark); 482 return null; 483 } 484 toJSON() { 485 return { stepType: "addMark", mark: this.mark.toJSON(), 486 from: this.from, to: this.to }; 487 } 488 /** 489 @internal 490 */ 491 static fromJSON(schema, json) { 492 if (typeof json.from != "number" || typeof json.to != "number") 493 throw new RangeError("Invalid input for AddMarkStep.fromJSON"); 494 return new AddMarkStep(json.from, json.to, schema.markFromJSON(json.mark)); 495 } 496} 497Step.jsonID("addMark", AddMarkStep); 498/** 499Remove a mark from all inline content between two positions. 500*/ 501class RemoveMarkStep extends Step { 502 /** 503 Create a mark-removing step. 504 */ 505 constructor( 506 /** 507 The start of the unmarked range. 508 */ 509 from, 510 /** 511 The end of the unmarked range. 512 */ 513 to, 514 /** 515 The mark to remove. 516 */ 517 mark) { 518 super(); 519 this.from = from; 520 this.to = to; 521 this.mark = mark; 522 } 523 apply(doc) { 524 let oldSlice = doc.slice(this.from, this.to); 525 let slice = new Slice(mapFragment(oldSlice.content, node => { 526 return node.mark(this.mark.removeFromSet(node.marks)); 527 }, doc), oldSlice.openStart, oldSlice.openEnd); 528 return StepResult.fromReplace(doc, this.from, this.to, slice); 529 } 530 invert() { 531 return new AddMarkStep(this.from, this.to, this.mark); 532 } 533 map(mapping) { 534 let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1); 535 if (from.deleted && to.deleted || from.pos >= to.pos) 536 return null; 537 return new RemoveMarkStep(from.pos, to.pos, this.mark); 538 } 539 merge(other) { 540 if (other instanceof RemoveMarkStep && 541 other.mark.eq(this.mark) && 542 this.from <= other.to && this.to >= other.from) 543 return new RemoveMarkStep(Math.min(this.from, other.from), Math.max(this.to, other.to), this.mark); 544 return null; 545 } 546 toJSON() { 547 return { stepType: "removeMark", mark: this.mark.toJSON(), 548 from: this.from, to: this.to }; 549 } 550 /** 551 @internal 552 */ 553 static fromJSON(schema, json) { 554 if (typeof json.from != "number" || typeof json.to != "number") 555 throw new RangeError("Invalid input for RemoveMarkStep.fromJSON"); 556 return new RemoveMarkStep(json.from, json.to, schema.markFromJSON(json.mark)); 557 } 558} 559Step.jsonID("removeMark", RemoveMarkStep); 560/** 561Add a mark to a specific node. 562*/ 563class AddNodeMarkStep extends Step { 564 /** 565 Create a node mark step. 566 */ 567 constructor( 568 /** 569 The position of the target node. 570 */ 571 pos, 572 /** 573 The mark to add. 574 */ 575 mark) { 576 super(); 577 this.pos = pos; 578 this.mark = mark; 579 } 580 apply(doc) { 581 let node = doc.nodeAt(this.pos); 582 if (!node) 583 return StepResult.fail("No node at mark step's position"); 584 let updated = node.type.create(node.attrs, null, this.mark.addToSet(node.marks)); 585 return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1)); 586 } 587 invert(doc) { 588 let node = doc.nodeAt(this.pos); 589 if (node) { 590 let newSet = this.mark.addToSet(node.marks); 591 if (newSet.length == node.marks.length) { 592 for (let i = 0; i < node.marks.length; i++) 593 if (!node.marks[i].isInSet(newSet)) 594 return new AddNodeMarkStep(this.pos, node.marks[i]); 595 return new AddNodeMarkStep(this.pos, this.mark); 596 } 597 } 598 return new RemoveNodeMarkStep(this.pos, this.mark); 599 } 600 map(mapping) { 601 let pos = mapping.mapResult(this.pos, 1); 602 return pos.deletedAfter ? null : new AddNodeMarkStep(pos.pos, this.mark); 603 } 604 toJSON() { 605 return { stepType: "addNodeMark", pos: this.pos, mark: this.mark.toJSON() }; 606 } 607 /** 608 @internal 609 */ 610 static fromJSON(schema, json) { 611 if (typeof json.pos != "number") 612 throw new RangeError("Invalid input for AddNodeMarkStep.fromJSON"); 613 return new AddNodeMarkStep(json.pos, schema.markFromJSON(json.mark)); 614 } 615} 616Step.jsonID("addNodeMark", AddNodeMarkStep); 617/** 618Remove a mark from a specific node. 619*/ 620class RemoveNodeMarkStep extends Step { 621 /** 622 Create a mark-removing step. 623 */ 624 constructor( 625 /** 626 The position of the target node. 627 */ 628 pos, 629 /** 630 The mark to remove. 631 */ 632 mark) { 633 super(); 634 this.pos = pos; 635 this.mark = mark; 636 } 637 apply(doc) { 638 let node = doc.nodeAt(this.pos); 639 if (!node) 640 return StepResult.fail("No node at mark step's position"); 641 let updated = node.type.create(node.attrs, null, this.mark.removeFromSet(node.marks)); 642 return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1)); 643 } 644 invert(doc) { 645 let node = doc.nodeAt(this.pos); 646 if (!node || !this.mark.isInSet(node.marks)) 647 return this; 648 return new AddNodeMarkStep(this.pos, this.mark); 649 } 650 map(mapping) { 651 let pos = mapping.mapResult(this.pos, 1); 652 return pos.deletedAfter ? null : new RemoveNodeMarkStep(pos.pos, this.mark); 653 } 654 toJSON() { 655 return { stepType: "removeNodeMark", pos: this.pos, mark: this.mark.toJSON() }; 656 } 657 /** 658 @internal 659 */ 660 static fromJSON(schema, json) { 661 if (typeof json.pos != "number") 662 throw new RangeError("Invalid input for RemoveNodeMarkStep.fromJSON"); 663 return new RemoveNodeMarkStep(json.pos, schema.markFromJSON(json.mark)); 664 } 665} 666Step.jsonID("removeNodeMark", RemoveNodeMarkStep); 667 668/** 669Replace a part of the document with a slice of new content. 670*/ 671class ReplaceStep extends Step { 672 /** 673 The given `slice` should fit the 'gap' between `from` and 674 `to`âthe depths must line up, and the surrounding nodes must be 675 able to be joined with the open sides of the slice. When 676 `structure` is true, the step will fail if the content between 677 from and to is not just a sequence of closing and then opening 678 tokens (this is to guard against rebased replace steps 679 overwriting something they weren't supposed to). 680 */ 681 constructor( 682 /** 683 The start position of the replaced range. 684 */ 685 from, 686 /** 687 The end position of the replaced range. 688 */ 689 to, 690 /** 691 The slice to insert. 692 */ 693 slice, 694 /** 695 @internal 696 */ 697 structure = false) { 698 super(); 699 this.from = from; 700 this.to = to; 701 this.slice = slice; 702 this.structure = structure; 703 } 704 apply(doc) { 705 if (this.structure && contentBetween(doc, this.from, this.to)) 706 return StepResult.fail("Structure replace would overwrite content"); 707 return StepResult.fromReplace(doc, this.from, this.to, this.slice); 708 } 709 getMap() { 710 return new StepMap([this.from, this.to - this.from, this.slice.size]); 711 } 712 invert(doc) { 713 return new ReplaceStep(this.from, this.from + this.slice.size, doc.slice(this.from, this.to)); 714 } 715 map(mapping) { 716 let to = mapping.mapResult(this.to, -1); 717 let from = this.from == this.to && ReplaceStep.MAP_BIAS < 0 ? to : mapping.mapResult(this.from, 1); 718 if (from.deletedAcross && to.deletedAcross) 719 return null; 720 return new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice, this.structure); 721 } 722 merge(other) { 723 if (!(other instanceof ReplaceStep) || other.structure || this.structure) 724 return null; 725 if (this.from + this.slice.size == other.from && !this.slice.openEnd && !other.slice.openStart) { 726 let slice = this.slice.size + other.slice.size == 0 ? Slice.empty 727 : new Slice(this.slice.content.append(other.slice.content), this.slice.openStart, other.slice.openEnd); 728 return new ReplaceStep(this.from, this.to + (other.to - other.from), slice, this.structure); 729 } 730 else if (other.to == this.from && !this.slice.openStart && !other.slice.openEnd) { 731 let slice = this.slice.size + other.slice.size == 0 ? Slice.empty 732 : new Slice(other.slice.content.append(this.slice.content), other.slice.openStart, this.slice.openEnd); 733 return new ReplaceStep(other.from, this.to, slice, this.structure); 734 } 735 else { 736 return null; 737 } 738 } 739 toJSON() { 740 let json = { stepType: "replace", from: this.from, to: this.to }; 741 if (this.slice.size) 742 json.slice = this.slice.toJSON(); 743 if (this.structure) 744 json.structure = true; 745 return json; 746 } 747 /** 748 @internal 749 */ 750 static fromJSON(schema, json) { 751 if (typeof json.from != "number" || typeof json.to != "number") 752 throw new RangeError("Invalid input for ReplaceStep.fromJSON"); 753 return new ReplaceStep(json.from, json.to, Slice.fromJSON(schema, json.slice), !!json.structure); 754 } 755} 756/** 757By default, for backwards compatibility, an inserting step 758mapped over an insertion at that same position fill move after 759the inserted content. In a collaborative editing situation, that 760can make redone insertions appear in unexpected places. You can 761set this to -1 to make such mapping keep the step before the 762insertion instead. 763*/ 764ReplaceStep.MAP_BIAS = 1; 765Step.jsonID("replace", ReplaceStep); 766/** 767Replace a part of the document with a slice of content, but 768preserve a range of the replaced content by moving it into the 769slice. 770*/ 771class ReplaceAroundStep extends Step { 772 /** 773 Create a replace-around step with the given range and gap. 774 `insert` should be the point in the slice into which the content 775 of the gap should be moved. `structure` has the same meaning as 776 it has in the [`ReplaceStep`](https://prosemirror.net/docs/ref/#transform.ReplaceStep) class. 777 */ 778 constructor( 779 /** 780 The start position of the replaced range. 781 */ 782 from, 783 /** 784 The end position of the replaced range. 785 */ 786 to, 787 /** 788 The start of preserved range. 789 */ 790 gapFrom, 791 /** 792 The end of preserved range. 793 */ 794 gapTo, 795 /** 796 The slice to insert. 797 */ 798 slice, 799 /** 800 The position in the slice where the preserved range should be 801 inserted. 802 */ 803 insert, 804 /** 805 @internal 806 */ 807 structure = false) { 808 super(); 809 this.from = from; 810 this.to = to; 811 this.gapFrom = gapFrom; 812 this.gapTo = gapTo; 813 this.slice = slice; 814 this.insert = insert; 815 this.structure = structure; 816 } 817 apply(doc) { 818 if (this.structure && (contentBetween(doc, this.from, this.gapFrom) || 819 contentBetween(doc, this.gapTo, this.to))) 820 return StepResult.fail("Structure gap-replace would overwrite content"); 821 let gap = doc.slice(this.gapFrom, this.gapTo); 822 if (gap.openStart || gap.openEnd) 823 return StepResult.fail("Gap is not a flat range"); 824 let inserted = this.slice.insertAt(this.insert, gap.content); 825 if (!inserted) 826 return StepResult.fail("Content does not fit in gap"); 827 return StepResult.fromReplace(doc, this.from, this.to, inserted); 828 } 829 getMap() { 830 return new StepMap([this.from, this.gapFrom - this.from, this.insert, 831 this.gapTo, this.to - this.gapTo, this.slice.size - this.insert]); 832 } 833 invert(doc) { 834 let gap = this.gapTo - this.gapFrom; 835 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); 836 } 837 map(mapping) { 838 let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1); 839 let gapFrom = this.from == this.gapFrom ? from.pos : mapping.map(this.gapFrom, -1); 840 let gapTo = this.to == this.gapTo ? to.pos : mapping.map(this.gapTo, 1); 841 if ((from.deletedAcross && to.deletedAcross) || gapFrom < from.pos || gapTo > to.pos) 842 return null; 843 return new ReplaceAroundStep(from.pos, to.pos, gapFrom, gapTo, this.slice, this.insert, this.structure); 844 } 845 toJSON() { 846 let json = { stepType: "replaceAround", from: this.from, to: this.to, 847 gapFrom: this.gapFrom, gapTo: this.gapTo, insert: this.insert }; 848 if (this.slice.size) 849 json.slice = this.slice.toJSON(); 850 if (this.structure) 851 json.structure = true; 852 return json; 853 } 854 /** 855 @internal 856 */ 857 static fromJSON(schema, json) { 858 if (typeof json.from != "number" || typeof json.to != "number" || 859 typeof json.gapFrom != "number" || typeof json.gapTo != "number" || typeof json.insert != "number") 860 throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON"); 861 return new ReplaceAroundStep(json.from, json.to, json.gapFrom, json.gapTo, Slice.fromJSON(schema, json.slice), json.insert, !!json.structure); 862 } 863} 864Step.jsonID("replaceAround", ReplaceAroundStep); 865function contentBetween(doc, from, to) { 866 let $from = doc.resolve(from), dist = to - from, depth = $from.depth; 867 while (dist > 0 && depth > 0 && $from.indexAfter(depth) == $from.node(depth).childCount) { 868 depth--; 869 dist--; 870 } 871 if (dist > 0) { 872 let next = $from.node(depth).maybeChild($from.indexAfter(depth)); 873 while (dist > 0) { 874 if (!next || next.isLeaf) 875 return true; 876 next = next.firstChild; 877 dist--; 878 } 879 } 880 return false; 881} 882 883function addMark(tr, from, to, mark) { 884 let removed = [], added = []; 885 let removing, adding; 886 tr.doc.nodesBetween(from, to, (node, pos, parent) => { 887 if (!node.isInline) 888 return; 889 let marks = node.marks; 890 if (!mark.isInSet(marks) && parent.type.allowsMarkType(mark.type)) { 891 let start = Math.max(pos, from), end = Math.min(pos + node.nodeSize, to); 892 let newSet = mark.addToSet(marks); 893 for (let i = 0; i < marks.length; i++) { 894 if (!marks[i].isInSet(newSet)) { 895 if (removing && removing.to == start && removing.mark.eq(marks[i])) 896 removing.to = end; 897 else 898 removed.push(removing = new RemoveMarkStep(start, end, marks[i])); 899 } 900 } 901 if (adding && adding.to == start) 902 adding.to = end; 903 else 904 added.push(adding = new AddMarkStep(start, end, mark)); 905 } 906 }); 907 removed.forEach(s => tr.step(s)); 908 added.forEach(s => tr.step(s)); 909} 910function removeMark(tr, from, to, mark) { 911 let matched = [], step = 0; 912 tr.doc.nodesBetween(from, to, (node, pos) => { 913 if (!node.isInline) 914 return; 915 step++; 916 let toRemove = null; 917 if (mark instanceof MarkType) { 918 let set = node.marks, found; 919 while (found = mark.isInSet(set)) { 920 (toRemove || (toRemove = [])).push(found); 921 set = found.removeFromSet(set); 922 } 923 } 924 else if (mark) { 925 if (mark.isInSet(node.marks)) 926 toRemove = [mark]; 927 } 928 else { 929 toRemove = node.marks; 930 } 931 if (toRemove && toRemove.length) { 932 let end = Math.min(pos + node.nodeSize, to); 933 for (let i = 0; i < toRemove.length; i++) { 934 let style = toRemove[i], found; 935 for (let j = 0; j < matched.length; j++) { 936 let m = matched[j]; 937 if (m.step == step - 1 && style.eq(matched[j].style)) 938 found = m; 939 } 940 if (found) { 941 found.to = end; 942 found.step = step; 943 } 944 else { 945 matched.push({ style, from: Math.max(pos, from), to: end, step }); 946 } 947 } 948 } 949 }); 950 matched.forEach(m => tr.step(new RemoveMarkStep(m.from, m.to, m.style))); 951} 952function clearIncompatible(tr, pos, parentType, match = parentType.contentMatch, clearNewlines = true) { 953 let node = tr.doc.nodeAt(pos); 954 let replSteps = [], cur = pos + 1; 955 for (let i = 0; i < node.childCount; i++) { 956 let child = node.child(i), end = cur + child.nodeSize; 957 let allowed = match.matchType(child.type); 958 if (!allowed) { 959 replSteps.push(new ReplaceStep(cur, end, Slice.empty)); 960 } 961 else { 962 match = allowed; 963 for (let j = 0; j < child.marks.length; j++) 964 if (!parentType.allowsMarkType(child.marks[j].type)) 965 tr.step(new RemoveMarkStep(cur, end, child.marks[j])); 966 if (clearNewlines && child.isText && parentType.whitespace != "pre") { 967 let m, newline = /\r?\n|\r/g, slice; 968 while (m = newline.exec(child.text)) { 969 if (!slice) 970 slice = new Slice(Fragment.from(parentType.schema.text(" ", parentType.allowedMarks(child.marks))), 0, 0); 971 replSteps.push(new ReplaceStep(cur + m.index, cur + m.index + m[0].length, slice)); 972 } 973 } 974 } 975 cur = end; 976 } 977 if (!match.validEnd) { 978 let fill = match.fillBefore(Fragment.empty, true); 979 tr.replace(cur, cur, new Slice(fill, 0, 0)); 980 } 981 for (let i = replSteps.length - 1; i >= 0; i--) 982 tr.step(replSteps[i]); 983} 984 985function canCut(node, start, end) { 986 return (start == 0 || node.canReplace(start, node.childCount)) && 987 (end == node.childCount || node.canReplace(0, end)); 988} 989/** 990Try to find a target depth to which the content in the given range 991can be lifted. Will not go across 992[isolating](https://prosemirror.net/docs/ref/#model.NodeSpec.isolating) parent nodes. 993*/ 994function liftTarget(range) { 995 let parent = range.parent; 996 let content = parent.content.cutByIndex(range.startIndex, range.endIndex); 997 for (let depth = range.depth, contentBefore = 0, contentAfter = 0;; --depth) { 998 let node = range.$from.node(depth); 999 let index = range.$from.index(depth) + contentBefore, endIndex = range.$to.indexAfter(depth) - contentAfter; 1000 if (depth < range.depth && node.canReplace(index, endIndex, content)) 1001 return depth; 1002 if (depth == 0 || node.type.spec.isolating || !canCut(node, index, endIndex)) 1003 break; 1004 if (index) 1005 contentBefore = 1; 1006 if (endIndex < node.childCount) 1007 contentAfter = 1; 1008 } 1009 return null; 1010} 1011function lift(tr, range, target) { 1012 let { $from, $to, depth } = range; 1013 let gapStart = $from.before(depth + 1), gapEnd = $to.after(depth + 1); 1014 let start = gapStart, end = gapEnd; 1015 let before = Fragment.empty, openStart = 0; 1016 for (let d = depth, splitting = false; d > target; d--) 1017 if (splitting || $from.index(d) > 0) { 1018 splitting = true; 1019 before = Fragment.from($from.node(d).copy(before)); 1020 openStart++; 1021 } 1022 else { 1023 start--; 1024 } 1025 let after = Fragment.empty, openEnd = 0; 1026 for (let d = depth, splitting = false; d > target; d--) 1027 if (splitting || $to.after(d + 1) < $to.end(d)) { 1028 splitting = true; 1029 after = Fragment.from($to.node(d).copy(after)); 1030 openEnd++; 1031 } 1032 else { 1033 end++; 1034 } 1035 tr.step(new ReplaceAroundStep(start, end, gapStart, gapEnd, new Slice(before.append(after), openStart, openEnd), before.size - openStart, true)); 1036} 1037/** 1038Try to find a valid way to wrap the content in the given range in a 1039node of the given type. May introduce extra nodes around and inside 1040the wrapper node, if necessary. Returns null if no valid wrapping 1041could be found. When `innerRange` is given, that range's content is 1042used as the content to fit into the wrapping, instead of the 1043content of `range`. 1044*/ 1045function findWrapping(range, nodeType, attrs = null, innerRange = range) { 1046 let around = findWrappingOutside(range, nodeType); 1047 let inner = around && findWrappingInside(innerRange, nodeType); 1048 if (!inner) 1049 return null; 1050 return around.map(withAttrs) 1051 .concat({ type: nodeType, attrs }).concat(inner.map(withAttrs)); 1052} 1053function withAttrs(type) { return { type, attrs: null }; } 1054function findWrappingOutside(range, type) { 1055 let { parent, startIndex, endIndex } = range; 1056 let around = parent.contentMatchAt(startIndex).findWrapping(type); 1057 if (!around) 1058 return null; 1059 let outer = around.length ? around[0] : type; 1060 return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null; 1061} 1062function findWrappingInside(range, type) { 1063 let { parent, startIndex, endIndex } = range; 1064 let inner = parent.child(startIndex); 1065 let inside = type.contentMatch.findWrapping(inner.type); 1066 if (!inside) 1067 return null; 1068 let lastType = inside.length ? inside[inside.length - 1] : type; 1069 let innerMatch = lastType.contentMatch; 1070 for (let i = startIndex; innerMatch && i < endIndex; i++) 1071 innerMatch = innerMatch.matchType(parent.child(i).type); 1072 if (!innerMatch || !innerMatch.validEnd) 1073 return null; 1074 return inside; 1075} 1076function wrap(tr, range, wrappers) { 1077 let content = Fragment.empty; 1078 for (let i = wrappers.length - 1; i >= 0; i--) { 1079 if (content.size) { 1080 let match = wrappers[i].type.contentMatch.matchFragment(content); 1081 if (!match || !match.validEnd) 1082 throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper"); 1083 } 1084 content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content)); 1085 } 1086 let start = range.start, end = range.end; 1087 tr.step(new ReplaceAroundStep(start, end, start, end, new Slice(content, 0, 0), wrappers.length, true)); 1088} 1089function setBlockType(tr, from, to, type, attrs) { 1090 if (!type.isTextblock) 1091 throw new RangeError("Type given to setBlockType should be a textblock"); 1092 let mapFrom = tr.steps.length; 1093 tr.doc.nodesBetween(from, to, (node, pos) => { 1094 let attrsHere = typeof attrs == "function" ? attrs(node) : attrs; 1095 if (node.isTextblock && !node.hasMarkup(type, attrsHere) && 1096 canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) { 1097 let convertNewlines = null; 1098 if (type.schema.linebreakReplacement) { 1099 let pre = type.whitespace == "pre", supportLinebreak = !!type.contentMatch.matchType(type.schema.linebreakReplacement); 1100 if (pre && !supportLinebreak) 1101 convertNewlines = false; 1102 else if (!pre && supportLinebreak) 1103 convertNewlines = true; 1104 } 1105 // Ensure all markup that isn't allowed in the new node type is cleared 1106 if (convertNewlines === false) 1107 replaceLinebreaks(tr, node, pos, mapFrom); 1108 clearIncompatible(tr, tr.mapping.slice(mapFrom).map(pos, 1), type, undefined, convertNewlines === null); 1109 let mapping = tr.mapping.slice(mapFrom); 1110 let startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1); 1111 tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1, new Slice(Fragment.from(type.create(attrsHere, null, node.marks)), 0, 0), 1, true)); 1112 if (convertNewlines === true) 1113 replaceNewlines(tr, node, pos, mapFrom); 1114 return false; 1115 } 1116 }); 1117} 1118function replaceNewlines(tr, node, pos, mapFrom) { 1119 node.forEach((child, offset) => { 1120 if (child.isText) { 1121 let m, newline = /\r?\n|\r/g; 1122 while (m = newline.exec(child.text)) { 1123 let start = tr.mapping.slice(mapFrom).map(pos + 1 + offset + m.index); 1124 tr.replaceWith(start, start + 1, node.type.schema.linebreakReplacement.create()); 1125 } 1126 } 1127 }); 1128} 1129function replaceLinebreaks(tr, node, pos, mapFrom) { 1130 node.forEach((child, offset) => { 1131 if (child.type == child.type.schema.linebreakReplacement) { 1132 let start = tr.mapping.slice(mapFrom).map(pos + 1 + offset); 1133 tr.replaceWith(start, start + 1, node.type.schema.text("\n")); 1134 } 1135 }); 1136} 1137function canChangeType(doc, pos, type) { 1138 let $pos = doc.resolve(pos), index = $pos.index(); 1139 return $pos.parent.canReplaceWith(index, index + 1, type); 1140} 1141/** 1142Change the type, attributes, and/or marks of the node at `pos`. 1143When `type` isn't given, the existing node type is preserved, 1144*/ 1145function setNodeMarkup(tr, pos, type, attrs, marks) { 1146 let node = tr.doc.nodeAt(pos); 1147 if (!node) 1148 throw new RangeError("No node at given position"); 1149 if (!type) 1150 type = node.type; 1151 let newNode = type.create(attrs, null, marks || node.marks); 1152 if (node.isLeaf) 1153 return tr.replaceWith(pos, pos + node.nodeSize, newNode); 1154 if (!type.validContent(node.content)) 1155 throw new RangeError("Invalid content for node type " + type.name); 1156 tr.step(new ReplaceAroundStep(pos, pos + node.nodeSize, pos + 1, pos + node.nodeSize - 1, new Slice(Fragment.from(newNode), 0, 0), 1, true)); 1157} 1158/** 1159Check whether splitting at the given position is allowed. 1160*/ 1161function canSplit(doc, pos, depth = 1, typesAfter) { 1162 let $pos = doc.resolve(pos), base = $pos.depth - depth; 1163 let innerType = (typesAfter && typesAfter[typesAfter.length - 1]) || $pos.parent; 1164 if (base < 0 || $pos.parent.type.spec.isolating || 1165 !$pos.parent.canReplace($pos.index(), $pos.parent.childCount) || 1166 !innerType.type.validContent($pos.parent.content.cutByIndex($pos.index(), $pos.parent.childCount))) 1167 return false; 1168 for (let d = $pos.depth - 1, i = depth - 2; d > base; d--, i--) { 1169 let node = $pos.node(d), index = $pos.index(d); 1170 if (node.type.spec.isolating) 1171 return false; 1172 let rest = node.content.cutByIndex(index, node.childCount); 1173 let overrideChild = typesAfter && typesAfter[i + 1]; 1174 if (overrideChild) 1175 rest = rest.replaceChild(0, overrideChild.type.create(overrideChild.attrs)); 1176 let after = (typesAfter && typesAfter[i]) || node; 1177 if (!node.canReplace(index + 1, node.childCount) || !after.type.validContent(rest)) 1178 return false; 1179 } 1180 let index = $pos.indexAfter(base); 1181 let baseType = typesAfter && typesAfter[0]; 1182 return $pos.node(base).canReplaceWith(index, index, baseType ? baseType.type : $pos.node(base + 1).type); 1183} 1184function split(tr, pos, depth = 1, typesAfter) { 1185 let $pos = tr.doc.resolve(pos), before = Fragment.empty, after = Fragment.empty; 1186 for (let d = $pos.depth, e = $pos.depth - depth, i = depth - 1; d > e; d--, i--) { 1187 before = Fragment.from($pos.node(d).copy(before)); 1188 let typeAfter = typesAfter && typesAfter[i]; 1189 after = Fragment.from(typeAfter ? typeAfter.type.create(typeAfter.attrs, after) : $pos.node(d).copy(after)); 1190 } 1191 tr.step(new ReplaceStep(pos, pos, new Slice(before.append(after), depth, depth), true)); 1192} 1193/** 1194Test whether the blocks before and after a given position can be 1195joined. 1196*/ 1197function canJoin(doc, pos) { 1198 let $pos = doc.resolve(pos), index = $pos.index(); 1199 return joinable($pos.nodeBefore, $pos.nodeAfter) && 1200 $pos.parent.canReplace(index, index + 1); 1201} 1202function canAppendWithSubstitutedLinebreaks(a, b) { 1203 if (!b.content.size) 1204 a.type.compatibleContent(b.type); 1205 let match = a.contentMatchAt(a.childCount); 1206 let { linebreakReplacement } = a.type.schema; 1207 for (let i = 0; i < b.childCount; i++) { 1208 let child = b.child(i); 1209 let type = child.type == linebreakReplacement ? a.type.schema.nodes.text : child.type; 1210 match = match.matchType(type); 1211 if (!match) 1212 return false; 1213 if (!a.type.allowsMarks(child.marks)) 1214 return false; 1215 } 1216 return match.validEnd; 1217} 1218function joinable(a, b) { 1219 return !!(a && b && !a.isLeaf && canAppendWithSubstitutedLinebreaks(a, b)); 1220} 1221/** 1222Find an ancestor of the given position that can be joined to the 1223block before (or after if `dir` is positive). Returns the joinable 1224point, if any. 1225*/ 1226function joinPoint(doc, pos, dir = -1) { 1227 let $pos = doc.resolve(pos); 1228 for (let d = $pos.depth;; d--) { 1229 let before, after, index = $pos.index(d); 1230 if (d == $pos.depth) { 1231 before = $pos.nodeBefore; 1232 after = $pos.nodeAfter; 1233 } 1234 else if (dir > 0) { 1235 before = $pos.node(d + 1); 1236 index++; 1237 after = $pos.node(d).maybeChild(index); 1238 } 1239 else { 1240 before = $pos.node(d).maybeChild(index - 1); 1241 after = $pos.node(d + 1); 1242 } 1243 if (before && !before.isTextblock && joinable(before, after) && 1244 $pos.node(d).canReplace(index, index + 1)) 1245 return pos; 1246 if (d == 0) 1247 break; 1248 pos = dir < 0 ? $pos.before(d) : $pos.after(d); 1249 } 1250} 1251function join(tr, pos, depth) { 1252 let convertNewlines = null; 1253 let { linebreakReplacement } = tr.doc.type.schema; 1254 let $before = tr.doc.resolve(pos - depth), beforeType = $before.node().type; 1255 if (linebreakReplacement && beforeType.inlineContent) { 1256 let pre = beforeType.whitespace == "pre"; 1257 let supportLinebreak = !!beforeType.contentMatch.matchType(linebreakReplacement); 1258 if (pre && !supportLinebreak) 1259 convertNewlines = false; 1260 else if (!pre && supportLinebreak) 1261 convertNewlines = true; 1262 } 1263 let mapFrom = tr.steps.length; 1264 if (convertNewlines === false) { 1265 let $after = tr.doc.resolve(pos + depth); 1266 replaceLinebreaks(tr, $after.node(), $after.before(), mapFrom); 1267 } 1268 if (beforeType.inlineContent) 1269 clearIncompatible(tr, pos + depth - 1, beforeType, $before.node().contentMatchAt($before.index()), convertNewlines == null); 1270 let mapping = tr.mapping.slice(mapFrom), start = mapping.map(pos - depth); 1271 tr.step(new ReplaceStep(start, mapping.map(pos + depth, -1), Slice.empty, true)); 1272 if (convertNewlines === true) { 1273 let $full = tr.doc.resolve(start); 1274 replaceNewlines(tr, $full.node(), $full.before(), tr.steps.length); 1275 } 1276 return tr; 1277} 1278/** 1279Try to find a point where a node of the given type can be inserted 1280near `pos`, by searching up the node hierarchy when `pos` itself 1281isn't a valid place but is at the start or end of a node. Return 1282null if no position was found. 1283*/ 1284function insertPoint(doc, pos, nodeType) { 1285 let $pos = doc.resolve(pos); 1286 if ($pos.parent.canReplaceWith($pos.index(), $pos.index(), nodeType)) 1287 return pos; 1288 if ($pos.parentOffset == 0) 1289 for (let d = $pos.depth - 1; d >= 0; d--) { 1290 let index = $pos.index(d);
vendor: 13,288 bytes, lines 1291-1566
1291 if ($pos.node(d).canReplaceWith(index, index, nodeType)) 1292 return $pos.before(d + 1); 1293 if (index > 0) 1294 return null; 1295 } 1296 if ($pos.parentOffset == $pos.parent.content.size) 1297 for (let d = $pos.depth - 1; d >= 0; d--) { 1298 let index = $pos.indexAfter(d); 1299 if ($pos.node(d).canReplaceWith(index, index, nodeType)) 1300 return $pos.after(d + 1); 1301 if (index < $pos.node(d).childCount) 1302 return null; 1303 } 1304 return null; 1305} 1306/** 1307Finds a position at or around the given position where the given 1308slice can be inserted. Will look at parent nodes' nearest boundary 1309and try there, even if the original position wasn't directly at the 1310start or end of that node. Returns null when no position was found. 1311*/ 1312function dropPoint(doc, pos, slice) { 1313 let $pos = doc.resolve(pos); 1314 if (!slice.content.size) 1315 return pos; 1316 let content = slice.content; 1317 for (let i = 0; i < slice.openStart; i++) 1318 content = content.firstChild.content; 1319 for (let pass = 1; pass <= (slice.openStart == 0 && slice.size ? 2 : 1); pass++) { 1320 for (let d = $pos.depth; d >= 0; d--) { 1321 let bias = d == $pos.depth ? 0 : $pos.pos <= ($pos.start(d + 1) + $pos.end(d + 1)) / 2 ? -1 : 1; 1322 let insertPos = $pos.index(d) + (bias > 0 ? 1 : 0); 1323 let parent = $pos.node(d), fits = false; 1324 if (pass == 1) { 1325 fits = parent.canReplace(insertPos, insertPos, content); 1326 } 1327 else { 1328 let wrapping = parent.contentMatchAt(insertPos).findWrapping(content.firstChild.type); 1329 fits = wrapping && parent.canReplaceWith(insertPos, insertPos, wrapping[0]); 1330 } 1331 if (fits) 1332 return bias == 0 ? $pos.pos : bias < 0 ? $pos.before(d + 1) : $pos.after(d + 1); 1333 } 1334 } 1335 return null; 1336} 1337 1338/** 1339âFitâ a slice into a given position in the document, producing a 1340[step](https://prosemirror.net/docs/ref/#transform.Step) that inserts it. Will return null if 1341there's no meaningful way to insert the slice here, or inserting it 1342would be a no-op (an empty slice over an empty range). 1343*/ 1344function replaceStep(doc, from, to = from, slice = Slice.empty) { 1345 if (from == to && !slice.size) 1346 return null; 1347 let $from = doc.resolve(from), $to = doc.resolve(to); 1348 // Optimization -- avoid work if it's obvious that it's not needed. 1349 if (fitsTrivially($from, $to, slice)) 1350 return new ReplaceStep(from, to, slice); 1351 return new Fitter($from, $to, slice).fit(); 1352} 1353function fitsTrivially($from, $to, slice) { 1354 return !slice.openStart && !slice.openEnd && $from.start() == $to.start() && 1355 $from.parent.canReplace($from.index(), $to.index(), slice.content); 1356} 1357// Algorithm for 'placing' the elements of a slice into a gap: 1358// 1359// We consider the content of each node that is open to the left to be 1360// independently placeable. I.e. in <p("foo"), p("bar")>, when the 1361// paragraph on the left is open, "foo" can be placed (somewhere on 1362// the left side of the replacement gap) independently from p("bar"). 1363// 1364// This class tracks the state of the placement progress in the 1365// following properties: 1366// 1367// - `frontier` holds a stack of `{type, match}` objects that 1368// represent the open side of the replacement. It starts at 1369// `$from`, then moves forward as content is placed, and is finally 1370// reconciled with `$to`. 1371// 1372// - `unplaced` is a slice that represents the content that hasn't 1373// been placed yet. 1374// 1375// - `placed` is a fragment of placed content. Its open-start value 1376// is implicit in `$from`, and its open-end value in `frontier`. 1377class Fitter { 1378 constructor($from, $to, unplaced) { 1379 this.$from = $from; 1380 this.$to = $to; 1381 this.unplaced = unplaced; 1382 this.frontier = []; 1383 this.placed = Fragment.empty; 1384 for (let i = 0; i <= $from.depth; i++) { 1385 let node = $from.node(i); 1386 this.frontier.push({ 1387 type: node.type, 1388 match: node.contentMatchAt($from.indexAfter(i)) 1389 }); 1390 } 1391 for (let i = $from.depth; i > 0; i--) 1392 this.placed = Fragment.from($from.node(i).copy(this.placed)); 1393 } 1394 get depth() { return this.frontier.length - 1; } 1395 fit() { 1396 // As long as there's unplaced content, try to place some of it. 1397 // If that fails, either increase the open score of the unplaced 1398 // slice, or drop nodes from it, and then try again. 1399 while (this.unplaced.size) { 1400 let fit = this.findFittable(); 1401 if (fit) 1402 this.placeNodes(fit); 1403 else 1404 this.openMore() || this.dropNode(); 1405 } 1406 // When there's inline content directly after the frontier _and_ 1407 // directly after `this.$to`, we must generate a `ReplaceAround` 1408 // step that pulls that content into the node after the frontier. 1409 // That means the fitting must be done to the end of the textblock 1410 // node after `this.$to`, not `this.$to` itself. 1411 let moveInline = this.mustMoveInline(), placedSize = this.placed.size - this.depth - this.$from.depth; 1412 let $from = this.$from, $to = this.close(moveInline < 0 ? this.$to : $from.doc.resolve(moveInline)); 1413 if (!$to) 1414 return null; 1415 // If closing to `$to` succeeded, create a step 1416 let content = this.placed, openStart = $from.depth, openEnd = $to.depth; 1417 while (openStart && openEnd && content.childCount == 1) { // Normalize by dropping open parent nodes 1418 content = content.firstChild.content; 1419 openStart--; 1420 openEnd--; 1421 } 1422 let slice = new Slice(content, openStart, openEnd); 1423 if (moveInline > -1) 1424 return new ReplaceAroundStep($from.pos, moveInline, this.$to.pos, this.$to.end(), slice, placedSize); 1425 if (slice.size || $from.pos != this.$to.pos) // Don't generate no-op steps 1426 return new ReplaceStep($from.pos, $to.pos, slice); 1427 return null; 1428 } 1429 // Find a position on the start spine of `this.unplaced` that has 1430 // content that can be moved somewhere on the frontier. Returns two 1431 // depths, one for the slice and one for the frontier. 1432 findFittable() { 1433 let startDepth = this.unplaced.openStart; 1434 for (let cur = this.unplaced.content, d = 0, openEnd = this.unplaced.openEnd; d < startDepth; d++) { 1435 let node = cur.firstChild; 1436 if (cur.childCount > 1) 1437 openEnd = 0; 1438 if (node.type.spec.isolating && openEnd <= d) { 1439 startDepth = d; 1440 break; 1441 } 1442 cur = node.content; 1443 } 1444 // Only try wrapping nodes (pass 2) after finding a place without 1445 // wrapping failed. 1446 for (let pass = 1; pass <= 2; pass++) { 1447 for (let sliceDepth = pass == 1 ? startDepth : this.unplaced.openStart; sliceDepth >= 0; sliceDepth--) { 1448 let fragment, parent = null; 1449 if (sliceDepth) { 1450 parent = contentAt(this.unplaced.content, sliceDepth - 1).firstChild; 1451 fragment = parent.content; 1452 } 1453 else { 1454 fragment = this.unplaced.content; 1455 } 1456 let first = fragment.firstChild; 1457 for (let frontierDepth = this.depth; frontierDepth >= 0; frontierDepth--) { 1458 let { type, match } = this.frontier[frontierDepth], wrap, inject = null; 1459 // In pass 1, if the next node matches, or there is no next 1460 // node but the parents look compatible, we've found a 1461 // place. 1462 if (pass == 1 && (first ? match.matchType(first.type) || (inject = match.fillBefore(Fragment.from(first), false)) 1463 : parent && type.compatibleContent(parent.type))) 1464 return { sliceDepth, frontierDepth, parent, inject }; 1465 // In pass 2, look for a set of wrapping nodes that make 1466 // `first` fit here. 1467 else if (pass == 2 && first && (wrap = match.findWrapping(first.type))) 1468 return { sliceDepth, frontierDepth, parent, wrap }; 1469 // Don't continue looking further up if the parent node 1470 // would fit here. 1471 if (parent && match.matchType(parent.type)) 1472 break; 1473 } 1474 } 1475 } 1476 } 1477 openMore() { 1478 let { content, openStart, openEnd } = this.unplaced; 1479 let inner = contentAt(content, openStart); 1480 if (!inner.childCount || inner.firstChild.isLeaf) 1481 return false; 1482 this.unplaced = new Slice(content, openStart + 1, Math.max(openEnd, inner.size + openStart >= content.size - openEnd ? openStart + 1 : 0)); 1483 return true; 1484 } 1485 dropNode() { 1486 let { content, openStart, openEnd } = this.unplaced; 1487 let inner = contentAt(content, openStart); 1488 if (inner.childCount <= 1 && openStart > 0) { 1489 let openAtEnd = content.size - openStart <= openStart + inner.size; 1490 this.unplaced = new Slice(dropFromFragment(content, openStart - 1, 1), openStart - 1, openAtEnd ? openStart - 1 : openEnd); 1491 } 1492 else { 1493 this.unplaced = new Slice(dropFromFragment(content, openStart, 1), openStart, openEnd); 1494 } 1495 } 1496 // Move content from the unplaced slice at `sliceDepth` to the 1497 // frontier node at `frontierDepth`. Close that frontier node when 1498 // applicable. 1499 placeNodes({ sliceDepth, frontierDepth, parent, inject, wrap }) { 1500 while (this.depth > frontierDepth) 1501 this.closeFrontierNode(); 1502 if (wrap) 1503 for (let i = 0; i < wrap.length; i++) 1504 this.openFrontierNode(wrap[i]); 1505 let slice = this.unplaced, fragment = parent ? parent.content : slice.content; 1506 let openStart = slice.openStart - sliceDepth; 1507 let taken = 0, add = []; 1508 let { match, type } = this.frontier[frontierDepth]; 1509 if (inject) { 1510 for (let i = 0; i < inject.childCount; i++) 1511 add.push(inject.child(i)); 1512 match = match.matchFragment(inject); 1513 } 1514 // Computes the amount of (end) open nodes at the end of the 1515 // fragment. When 0, the parent is open, but no more. When 1516 // negative, nothing is open. 1517 let openEndCount = (fragment.size + sliceDepth) - (slice.content.size - slice.openEnd); 1518 // Scan over the fragment, fitting as many child nodes as 1519 // possible. 1520 while (taken < fragment.childCount) { 1521 let next = fragment.child(taken), matches = match.matchType(next.type); 1522 if (!matches) 1523 break; 1524 taken++; 1525 if (taken > 1 || openStart == 0 || next.content.size) { // Drop empty open nodes 1526 match = matches; 1527 add.push(closeNodeStart(next.mark(type.allowedMarks(next.marks)), taken == 1 ? openStart : 0, taken == fragment.childCount ? openEndCount : -1)); 1528 } 1529 } 1530 let toEnd = taken == fragment.childCount; 1531 if (!toEnd) 1532 openEndCount = -1; 1533 this.placed = addToFragment(this.placed, frontierDepth, Fragment.from(add)); 1534 this.frontier[frontierDepth].match = match; 1535 // If the parent types match, and the entire node was moved, and 1536 // it's not open, close this frontier node right away. 1537 if (toEnd && openEndCount < 0 && parent && parent.type == this.frontier[this.depth].type && this.frontier.length > 1) 1538 this.closeFrontierNode(); 1539 // Add new frontier nodes for any open nodes at the end. 1540 for (let i = 0, cur = fragment; i < openEndCount; i++) { 1541 let node = cur.lastChild; 1542 this.frontier.push({ type: node.type, match: node.contentMatchAt(node.childCount) }); 1543 cur = node.content; 1544 } 1545 // Update `this.unplaced`. Drop the entire node from which we 1546 // placed it we got to its end, otherwise just drop the placed 1547 // nodes. 1548 this.unplaced = !toEnd ? new Slice(dropFromFragment(slice.content, sliceDepth, taken), slice.openStart, slice.openEnd) 1549 : sliceDepth == 0 ? Slice.empty 1550 : new Slice(dropFromFragment(slice.content, sliceDepth - 1, 1), sliceDepth - 1, openEndCount < 0 ? slice.openEnd : sliceDepth - 1); 1551 } 1552 mustMoveInline() { 1553 if (!this.$to.parent.isTextblock) 1554 return -1; 1555 let top = this.frontier[this.depth], level; 1556 if (!top.type.isTextblock || !contentAfterFits(this.$to, this.$to.depth, top.type, top.match, false) || 1557 (this.$to.depth == this.depth && (level = this.findCloseLevel(this.$to)) && level.depth == this.depth)) 1558 return -1; 1559 let { depth } = this.$to, after = this.$to.after(depth); 1560 while (depth > 1 && after == this.$to.end(--depth)) 1561 ++after; 1562 return after; 1563 } 1564 findCloseLevel($to) { 1565 scan: for (let i = Math.min(this.depth, $to.depth); i >= 0; i--) { 1566 let { match, type }
vendor: 3,377 bytes, lines 1566-1642
1566 = this.frontier[i]; 1567 let dropInner = i < $to.depth && $to.end(i + 1) == $to.pos + ($to.depth - (i + 1)); 1568 let fit = contentAfterFits($to, i, type, match, dropInner); 1569 if (!fit) 1570 continue; 1571 for (let d = i - 1; d >= 0; d--) { 1572 let { match, type } = this.frontier[d]; 1573 let matches = contentAfterFits($to, d, type, match, true); 1574 if (!matches || matches.childCount) 1575 continue scan; 1576 } 1577 return { depth: i, fit, move: dropInner ? $to.doc.resolve($to.after(i + 1)) : $to }; 1578 } 1579 } 1580 close($to) { 1581 let close = this.findCloseLevel($to); 1582 if (!close) 1583 return null; 1584 while (this.depth > close.depth) 1585 this.closeFrontierNode(); 1586 if (close.fit.childCount) 1587 this.placed = addToFragment(this.placed, close.depth, close.fit); 1588 $to = close.move; 1589 for (let d = close.depth + 1; d <= $to.depth; d++) { 1590 let node = $to.node(d), add = node.type.contentMatch.fillBefore(node.content, true, $to.index(d)); 1591 this.openFrontierNode(node.type, node.attrs, add); 1592 } 1593 return $to; 1594 } 1595 openFrontierNode(type, attrs = null, content) { 1596 let top = this.frontier[this.depth]; 1597 top.match = top.match.matchType(type); 1598 this.placed = addToFragment(this.placed, this.depth, Fragment.from(type.create(attrs, content))); 1599 this.frontier.push({ type, match: type.contentMatch }); 1600 } 1601 closeFrontierNode() { 1602 let open = this.frontier.pop(); 1603 let add = open.match.fillBefore(Fragment.empty, true); 1604 if (add.childCount) 1605 this.placed = addToFragment(this.placed, this.frontier.length, add); 1606 } 1607} 1608function dropFromFragment(fragment, depth, count) { 1609 if (depth == 0) 1610 return fragment.cutByIndex(count, fragment.childCount); 1611 return fragment.replaceChild(0, fragment.firstChild.copy(dropFromFragment(fragment.firstChild.content, depth - 1, count))); 1612} 1613function addToFragment(fragment, depth, content) { 1614 if (depth == 0) 1615 return fragment.append(content); 1616 return fragment.replaceChild(fragment.childCount - 1, fragment.lastChild.copy(addToFragment(fragment.lastChild.content, depth - 1, content))); 1617} 1618function contentAt(fragment, depth) { 1619 for (let i = 0; i < depth; i++) 1620 fragment = fragment.firstChild.content; 1621 return fragment; 1622} 1623function closeNodeStart(node, openStart, openEnd) { 1624 if (openStart <= 0) 1625 return node; 1626 let frag = node.content; 1627 if (openStart > 1) 1628 frag = frag.replaceChild(0, closeNodeStart(frag.firstChild, openStart - 1, frag.childCount == 1 ? openEnd - 1 : 0)); 1629 if (openStart > 0) { 1630 frag = node.type.contentMatch.fillBefore(frag).append(frag); 1631 if (openEnd <= 0) 1632 frag = frag.append(node.type.contentMatch.matchFragment(frag).fillBefore(Fragment.empty, true)); 1633 } 1634 return node.copy(frag); 1635} 1636function contentAfterFits($to, depth, type, match, open) { 1637 let node = $to.node(depth), index = open ? $to.indexAfter(depth) : $to.index(depth); 1638 if (index == node.childCount && !type.compatibleContent(node.type)) 1639 return null; 1640 let fit = match.fillBefore(node.content, true, index); 1641 return fit && !invalidMarks(type, node.content, index) ? fit : null; 1642}
vendor: 4,135 bytes, lines 1643-1734
1643function invalidMarks(type, fragment, start) { 1644 for (let i = start; i < fragment.childCount; i++) 1645 if (!type.allowsMarks(fragment.child(i).marks)) 1646 return true; 1647 return false; 1648} 1649function definesContent(type) { 1650 return type.spec.defining || type.spec.definingForContent; 1651} 1652function replaceRange(tr, from, to, slice) { 1653 if (!slice.size) 1654 return tr.deleteRange(from, to); 1655 let $from = tr.doc.resolve(from), $to = tr.doc.resolve(to); 1656 if (fitsTrivially($from, $to, slice)) 1657 return tr.step(new ReplaceStep(from, to, slice)); 1658 let targetDepths = coveredDepths($from, $to); 1659 // Can't replace the whole document, so remove 0 if it's present 1660 if (targetDepths[targetDepths.length - 1] == 0) 1661 targetDepths.pop(); 1662 // Negative numbers represent not expansion over the whole node at 1663 // that depth, but replacing from $from.before(-D) to $to.pos. 1664 let preferredTarget = -($from.depth + 1); 1665 targetDepths.unshift(preferredTarget); 1666 // This loop picks a preferred target depth, if one of the covering 1667 // depths is not outside of a defining node, and adds negative 1668 // depths for any depth that has $from at its start and does not 1669 // cross a defining node. 1670 for (let d = $from.depth, pos = $from.pos - 1; d > 0; d--, pos--) { 1671 let spec = $from.node(d).type.spec; 1672 if (spec.defining || spec.definingAsContext || spec.isolating) 1673 break; 1674 if (targetDepths.indexOf(d) > -1) 1675 preferredTarget = d; 1676 else if ($from.before(d) == pos) 1677 targetDepths.splice(1, 0, -d); 1678 } 1679 // Try to fit each possible depth of the slice into each possible 1680 // target depth, starting with the preferred depths. 1681 let preferredTargetIndex = targetDepths.indexOf(preferredTarget); 1682 let leftNodes = [], preferredDepth = slice.openStart; 1683 for (let content = slice.content, i = 0;; i++) { 1684 let node = content.firstChild; 1685 leftNodes.push(node); 1686 if (i == slice.openStart) 1687 break; 1688 content = node.content; 1689 } 1690 // Back up preferredDepth to cover defining textblocks directly 1691 // above it, possibly skipping a non-defining textblock. 1692 for (let d = preferredDepth - 1; d >= 0; d--) { 1693 let leftNode = leftNodes[d], def = definesContent(leftNode.type); 1694 if (def && !leftNode.sameMarkup($from.node(Math.abs(preferredTarget) - 1))) 1695 preferredDepth = d; 1696 else if (def || !leftNode.type.isTextblock) 1697 break; 1698 } 1699 for (let j = slice.openStart; j >= 0; j--) { 1700 let openDepth = (j + preferredDepth + 1) % (slice.openStart + 1); 1701 let insert = leftNodes[openDepth]; 1702 if (!insert) 1703 continue; 1704 for (let i = 0; i < targetDepths.length; i++) { 1705 // Loop over possible expansion levels, starting with the 1706 // preferred one 1707 let targetDepth = targetDepths[(i + preferredTargetIndex) % targetDepths.length], expand = true; 1708 if (targetDepth < 0) { 1709 expand = false; 1710 targetDepth = -targetDepth; 1711 } 1712 let parent = $from.node(targetDepth - 1), index = $from.index(targetDepth - 1); 1713 if (parent.canReplaceWith(index, index, insert.type, insert.marks)) 1714 return tr.replace($from.before(targetDepth), expand ? $to.after(targetDepth) : to, new Slice(closeFragment(slice.content, 0, slice.openStart, openDepth), openDepth, slice.openEnd)); 1715 } 1716 } 1717 let startSteps = tr.steps.length; 1718 for (let i = targetDepths.length - 1; i >= 0; i--) { 1719 tr.replace(from, to, slice); 1720 if (tr.steps.length > startSteps) 1721 break; 1722 let depth = targetDepths[i]; 1723 if (depth < 0) 1724 continue; 1725 from = $from.before(depth); 1726 to = $to.after(depth); 1727 } 1728} 1729function closeFragment(fragment, depth, oldOpen, newOpen, parent) { 1730 if (depth < oldOpen) { 1731 let first = fragment.firstChild; 1732 fragment = fragment.replaceChild(0, first.copy(closeFragment(first.content, depth + 1, oldOpen, newOpen, first))); 1733 } 1734 if (depth >
vendor: 16,649 bytes, lines 1734-2213
1734 newOpen) { 1735 let match = parent.contentMatchAt(0); 1736 let start = match.fillBefore(fragment).append(fragment); 1737 fragment = start.append(match.matchFragment(start).fillBefore(Fragment.empty, true)); 1738 } 1739 return fragment; 1740} 1741function replaceRangeWith(tr, from, to, node) { 1742 if (!node.isInline && from == to && tr.doc.resolve(from).parent.content.size) { 1743 let point = insertPoint(tr.doc, from, node.type); 1744 if (point != null) 1745 from = to = point; 1746 } 1747 tr.replaceRange(from, to, new Slice(Fragment.from(node), 0, 0)); 1748} 1749function deleteRange(tr, from, to) { 1750 let $from = tr.doc.resolve(from), $to = tr.doc.resolve(to); 1751 // When the deleted range spans from the start of one textblock to 1752 // the start of another one, move out of the start of both blocks. 1753 if ($from.parent.isTextblock && $to.parent.isTextblock && $from.start() != $to.start() && 1754 $from.parentOffset == 0 && $to.parentOffset == 0) { 1755 let shared = $from.sharedDepth(to), isolated = false; 1756 for (let d = $from.depth; d > shared; d--) 1757 if ($from.node(d).type.spec.isolating) 1758 isolated = true; 1759 for (let d = $to.depth; d > shared; d--) 1760 if ($to.node(d).type.spec.isolating) 1761 isolated = true; 1762 if (!isolated) { 1763 for (let d = $from.depth; d > 0 && from == $from.start(d); d--) 1764 from = $from.before(d); 1765 for (let d = $to.depth; d > 0 && to == $to.start(d); d--) 1766 to = $to.before(d); 1767 $from = tr.doc.resolve(from); 1768 $to = tr.doc.resolve(to); 1769 } 1770 } 1771 let covered = coveredDepths($from, $to); 1772 for (let i = 0; i < covered.length; i++) { 1773 let depth = covered[i], last = i == covered.length - 1; 1774 if ((last && depth == 0) || $from.node(depth).type.contentMatch.validEnd) 1775 return tr.delete($from.start(depth), $to.end(depth)); 1776 if (depth > 0 && (last || $from.node(depth - 1).canReplace($from.index(depth - 1), $to.indexAfter(depth - 1)))) 1777 return tr.delete($from.before(depth), $to.after(depth)); 1778 } 1779 for (let d = 1; d <= $from.depth && d <= $to.depth; d++) { 1780 if (from - $from.start(d) == $from.depth - d && to > $from.end(d) && $to.end(d) - to != $to.depth - d && 1781 $from.start(d - 1) == $to.start(d - 1) && $from.node(d - 1).canReplace($from.index(d - 1), $to.index(d - 1))) 1782 return tr.delete($from.before(d), to); 1783 } 1784 tr.delete(from, to); 1785} 1786// Returns an array of all depths for which $from - $to spans the 1787// whole content of the nodes at that depth. 1788function coveredDepths($from, $to) { 1789 let result = [], minDepth = Math.min($from.depth, $to.depth); 1790 for (let d = minDepth; d >= 0; d--) { 1791 let start = $from.start(d); 1792 if (start < $from.pos - ($from.depth - d) || 1793 $to.end(d) > $to.pos + ($to.depth - d) || 1794 $from.node(d).type.spec.isolating || 1795 $to.node(d).type.spec.isolating) 1796 break; 1797 if (start == $to.start(d) || 1798 (d == $from.depth && d == $to.depth && $from.parent.inlineContent && $to.parent.inlineContent && 1799 d && $to.start(d - 1) == start - 1)) 1800 result.push(d); 1801 } 1802 return result; 1803} 1804 1805/** 1806Update an attribute in a specific node. 1807*/ 1808class AttrStep extends Step { 1809 /** 1810 Construct an attribute step. 1811 */ 1812 constructor( 1813 /** 1814 The position of the target node. 1815 */ 1816 pos, 1817 /** 1818 The attribute to set. 1819 */ 1820 attr, 1821 // The attribute's new value. 1822 value) { 1823 super(); 1824 this.pos = pos; 1825 this.attr = attr; 1826 this.value = value; 1827 } 1828 apply(doc) { 1829 let node = doc.nodeAt(this.pos); 1830 if (!node) 1831 return StepResult.fail("No node at attribute step's position"); 1832 let attrs = Object.create(null); 1833 for (let name in node.attrs) 1834 attrs[name] = node.attrs[name]; 1835 attrs[this.attr] = this.value; 1836 let updated = node.type.create(attrs, null, node.marks); 1837 return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1)); 1838 } 1839 getMap() { 1840 return StepMap.empty; 1841 } 1842 invert(doc) { 1843 return new AttrStep(this.pos, this.attr, doc.nodeAt(this.pos).attrs[this.attr]); 1844 } 1845 map(mapping) { 1846 let pos = mapping.mapResult(this.pos, 1); 1847 return pos.deletedAfter ? null : new AttrStep(pos.pos, this.attr, this.value); 1848 } 1849 toJSON() { 1850 return { stepType: "attr", pos: this.pos, attr: this.attr, value: this.value }; 1851 } 1852 static fromJSON(schema, json) { 1853 if (typeof json.pos != "number" || typeof json.attr != "string") 1854 throw new RangeError("Invalid input for AttrStep.fromJSON"); 1855 return new AttrStep(json.pos, json.attr, json.value); 1856 } 1857} 1858Step.jsonID("attr", AttrStep); 1859/** 1860Update an attribute in the doc node. 1861*/ 1862class DocAttrStep extends Step { 1863 /** 1864 Construct an attribute step. 1865 */ 1866 constructor( 1867 /** 1868 The attribute to set. 1869 */ 1870 attr, 1871 // The attribute's new value. 1872 value) { 1873 super(); 1874 this.attr = attr; 1875 this.value = value; 1876 } 1877 apply(doc) { 1878 let attrs = Object.create(null); 1879 for (let name in doc.attrs) 1880 attrs[name] = doc.attrs[name]; 1881 attrs[this.attr] = this.value; 1882 let updated = doc.type.create(attrs, doc.content, doc.marks); 1883 return StepResult.ok(updated); 1884 } 1885 getMap() { 1886 return StepMap.empty; 1887 } 1888 invert(doc) { 1889 return new DocAttrStep(this.attr, doc.attrs[this.attr]); 1890 } 1891 map(mapping) { 1892 return this; 1893 } 1894 toJSON() { 1895 return { stepType: "docAttr", attr: this.attr, value: this.value }; 1896 } 1897 static fromJSON(schema, json) { 1898 if (typeof json.attr != "string") 1899 throw new RangeError("Invalid input for DocAttrStep.fromJSON"); 1900 return new DocAttrStep(json.attr, json.value); 1901 } 1902} 1903Step.jsonID("docAttr", DocAttrStep); 1904 1905/** 1906@internal 1907*/ 1908let TransformError = class extends Error { 1909}; 1910TransformError = function TransformError(message) { 1911 let err = Error.call(this, message); 1912 err.__proto__ = TransformError.prototype; 1913 return err; 1914}; 1915TransformError.prototype = Object.create(Error.prototype); 1916TransformError.prototype.constructor = TransformError; 1917TransformError.prototype.name = "TransformError"; 1918/** 1919Abstraction to build up and track an array of 1920[steps](https://prosemirror.net/docs/ref/#transform.Step) representing a document transformation. 1921 1922Most transforming methods return the `Transform` object itself, so 1923that they can be chained. 1924*/ 1925class Transform { 1926 /** 1927 Create a transform that starts with the given document. 1928 */ 1929 constructor( 1930 /** 1931 The current document (the result of applying the steps in the 1932 transform). 1933 */ 1934 doc) { 1935 this.doc = doc; 1936 /** 1937 The steps in this transform. 1938 */ 1939 this.steps = []; 1940 /** 1941 The documents before each of the steps. 1942 */ 1943 this.docs = []; 1944 /** 1945 A mapping with the maps for each of the steps in this transform. 1946 */ 1947 this.mapping = new Mapping; 1948 } 1949 /** 1950 The starting document. 1951 */ 1952 get before() { return this.docs.length ? this.docs[0] : this.doc; } 1953 /** 1954 Apply a new step in this transform, saving the result. Throws an 1955 error when the step fails. 1956 */ 1957 step(step) { 1958 let result = this.maybeStep(step); 1959 if (result.failed) 1960 throw new TransformError(result.failed); 1961 return this; 1962 } 1963 /** 1964 Try to apply a step in this transformation, ignoring it if it 1965 fails. Returns the step result. 1966 */ 1967 maybeStep(step) { 1968 let result = step.apply(this.doc); 1969 if (!result.failed) 1970 this.addStep(step, result.doc); 1971 return result; 1972 } 1973 /** 1974 True when the document has been changed (when there are any 1975 steps). 1976 */ 1977 get docChanged() { 1978 return this.steps.length > 0; 1979 } 1980 /** 1981 Return a single range, in post-transform document positions, 1982 that covers all content changed by this transform. Returns null 1983 if no replacements are made. Note that this will ignore changes 1984 that add/remove marks without replacing the underlying content. 1985 */ 1986 changedRange() { 1987 let from = 1e9, to = -1e9; 1988 for (let i = 0; i < this.mapping.maps.length; i++) { 1989 let map = this.mapping.maps[i]; 1990 if (i) { 1991 from = map.map(from, 1); 1992 to = map.map(to, -1); 1993 } 1994 map.forEach((_f, _t, fromB, toB) => { 1995 from = Math.min(from, fromB); 1996 to = Math.max(to, toB); 1997 }); 1998 } 1999 return from == 1e9 ? null : { from, to }; 2000 } 2001 /** 2002 @internal 2003 */ 2004 addStep(step, doc) { 2005 this.docs.push(this.doc); 2006 this.steps.push(step); 2007 this.mapping.appendMap(step.getMap()); 2008 this.doc = doc; 2009 } 2010 /** 2011 Replace the part of the document between `from` and `to` with the 2012 given `slice`. 2013 */ 2014 replace(from, to = from, slice = Slice.empty) { 2015 let step = replaceStep(this.doc, from, to, slice); 2016 if (step) 2017 this.step(step); 2018 return this; 2019 } 2020 /** 2021 Replace the given range with the given content, which may be a 2022 fragment, node, or array of nodes. 2023 */ 2024 replaceWith(from, to, content) { 2025 return this.replace(from, to, new Slice(Fragment.from(content), 0, 0)); 2026 } 2027 /** 2028 Delete the content between the given positions. 2029 */ 2030 delete(from, to) { 2031 return this.replace(from, to, Slice.empty); 2032 } 2033 /** 2034 Insert the given content at the given position. 2035 */ 2036 insert(pos, content) { 2037 return this.replaceWith(pos, pos, content); 2038 } 2039 /** 2040 Replace a range of the document with a given slice, using 2041 `from`, `to`, and the slice's 2042 [`openStart`](https://prosemirror.net/docs/ref/#model.Slice.openStart) property as hints, rather 2043 than fixed start and end points. This method may grow the 2044 replaced area or close open nodes in the slice in order to get a 2045 fit that is more in line with WYSIWYG expectations, by dropping 2046 fully covered parent nodes of the replaced region when they are 2047 marked [non-defining as 2048 context](https://prosemirror.net/docs/ref/#model.NodeSpec.definingAsContext), or including an 2049 open parent node from the slice that _is_ marked as [defining 2050 its content](https://prosemirror.net/docs/ref/#model.NodeSpec.definingForContent). 2051 2052 This is the method, for example, to handle paste. The similar 2053 [`replace`](https://prosemirror.net/docs/ref/#transform.Transform.replace) method is a more 2054 primitive tool which will _not_ move the start and end of its given 2055 range, and is useful in situations where you need more precise 2056 control over what happens. 2057 */ 2058 replaceRange(from, to, slice) { 2059 replaceRange(this, from, to, slice); 2060 return this; 2061 } 2062 /** 2063 Replace the given range with a node, but use `from` and `to` as 2064 hints, rather than precise positions. When from and to are the same 2065 and are at the start or end of a parent node in which the given 2066 node doesn't fit, this method may _move_ them out towards a parent 2067 that does allow the given node to be placed. When the given range 2068 completely covers a parent node, this method may completely replace 2069 that parent node. 2070 */ 2071 replaceRangeWith(from, to, node) { 2072 replaceRangeWith(this, from, to, node); 2073 return this; 2074 } 2075 /** 2076 Delete the given range, expanding it to cover fully covered 2077 parent nodes until a valid replace is found. 2078 */ 2079 deleteRange(from, to) { 2080 deleteRange(this, from, to); 2081 return this; 2082 } 2083 /** 2084 Split the content in the given range off from its parent, if there 2085 is sibling content before or after it, and move it up the tree to 2086 the depth specified by `target`. You'll probably want to use 2087 [`liftTarget`](https://prosemirror.net/docs/ref/#transform.liftTarget) to compute `target`, to make 2088 sure the lift is valid. 2089 */ 2090 lift(range, target) { 2091 lift(this, range, target); 2092 return this; 2093 } 2094 /** 2095 Join the blocks around the given position. If depth is 2, their 2096 last and first siblings are also joined, and so on. 2097 */ 2098 join(pos, depth = 1) { 2099 join(this, pos, depth); 2100 return this; 2101 } 2102 /** 2103 Wrap the given [range](https://prosemirror.net/docs/ref/#model.NodeRange) in the given set of wrappers. 2104 The wrappers are assumed to be valid in this position, and should 2105 probably be computed with [`findWrapping`](https://prosemirror.net/docs/ref/#transform.findWrapping). 2106 */ 2107 wrap(range, wrappers) { 2108 wrap(this, range, wrappers); 2109 return this; 2110 } 2111 /** 2112 Set the type of all textblocks (partly) between `from` and `to` to 2113 the given node type with the given attributes. 2114 */ 2115 setBlockType(from, to = from, type, attrs = null) { 2116 setBlockType(this, from, to, type, attrs); 2117 return this; 2118 } 2119 /** 2120 Change the type, attributes, and/or marks of the node at `pos`. 2121 When `type` isn't given, the existing node type is preserved, 2122 */ 2123 setNodeMarkup(pos, type, attrs = null, marks) { 2124 setNodeMarkup(this, pos, type, attrs, marks); 2125 return this; 2126 } 2127 /** 2128 Set a single attribute on a given node to a new value. 2129 The `pos` addresses the document content. Use `setDocAttribute` 2130 to set attributes on the document itself. 2131 */ 2132 setNodeAttribute(pos, attr, value) { 2133 this.step(new AttrStep(pos, attr, value)); 2134 return this; 2135 } 2136 /** 2137 Set a single attribute on the document to a new value. 2138 */ 2139 setDocAttribute(attr, value) { 2140 this.step(new DocAttrStep(attr, value)); 2141 return this; 2142 } 2143 /** 2144 Add a mark to the node at position `pos`. 2145 */ 2146 addNodeMark(pos, mark) { 2147 this.step(new AddNodeMarkStep(pos, mark)); 2148 return this; 2149 } 2150 /** 2151 Remove a mark (or all marks of the given type) from the node at 2152 position `pos`. 2153 */ 2154 removeNodeMark(pos, mark) { 2155 let node = this.doc.nodeAt(pos); 2156 if (!node) 2157 throw new RangeError("No node at position " + pos); 2158 if (mark instanceof Mark) { 2159 if (mark.isInSet(node.marks)) 2160 this.step(new RemoveNodeMarkStep(pos, mark)); 2161 } 2162 else { 2163 let set = node.marks, found, steps = []; 2164 while (found = mark.isInSet(set)) { 2165 steps.push(new RemoveNodeMarkStep(pos, found)); 2166 set = found.removeFromSet(set); 2167 } 2168 for (let i = steps.length - 1; i >= 0; i--) 2169 this.step(steps[i]); 2170 } 2171 return this; 2172 } 2173 /** 2174 Split the node at the given position, and optionally, if `depth` is 2175 greater than one, any number of nodes above that. By default, the 2176 parts split off will inherit the node type of the original node. 2177 This can be changed by passing an array of types and attributes to 2178 use after the split (with the outermost nodes coming first). 2179 */ 2180 split(pos, depth = 1, typesAfter) { 2181 split(this, pos, depth, typesAfter); 2182 return this; 2183 } 2184 /** 2185 Add the given mark to the inline content between `from` and `to`. 2186 */ 2187 addMark(from, to, mark) { 2188 addMark(this, from, to, mark); 2189 return this; 2190 } 2191 /** 2192 Remove marks from inline nodes between `from` and `to`. When 2193 `mark` is a single mark, remove precisely that mark. When it is 2194 a mark type, remove all marks of that type. When it is null, 2195 remove all marks of any type. 2196 */ 2197 removeMark(from, to, mark) { 2198 removeMark(this, from, to, mark); 2199 return this; 2200 } 2201 /** 2202 Removes all marks and nodes from the content of the node at 2203 `pos` that don't match the given new parent node type. Accepts 2204 an optional starting [content match](https://prosemirror.net/docs/ref/#model.ContentMatch) as 2205 third argument. 2206 */ 2207 clearIncompatible(pos, parentType, match) { 2208 clearIncompatible(this, pos, parentType, match); 2209 return this; 2210 } 2211} 2212 2213export { AddMarkStep, AddNodeMarkStep, AttrStep, DocAttrStep, MapResult, Mapping, RemoveMarkStep, RemoveNodeMarkStep, ReplaceAroundStep, ReplaceStep, Step, StepMap, StepResult, Transform, TransformError, canJoin, canSplit, dropPoint, findWrapping, insertPoint, joinPoint, liftTarget, replaceStep };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.