prosemirror-transform
Advanced tools
Comparing version 1.2.4 to 1.2.5-beta1
{ | ||
"name": "prosemirror-transform", | ||
"version": "1.2.4", | ||
"version": "1.2.5-beta1", | ||
"description": "ProseMirror document transformations", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -18,15 +18,3 @@ import {Fragment, Slice} from "prosemirror-model" | ||
if (fitsTrivially($from, $to, slice)) return new ReplaceStep(from, to, slice) | ||
let placed = placeSlice($from, slice) | ||
let fittedLeft = fitLeft($from, placed) | ||
let fitted = fitRight($from, $to, fittedLeft) | ||
if (!fitted) return null | ||
if (fittedLeft.size != fitted.size && canMoveText($from, $to, fittedLeft)) { | ||
let d = $to.depth, after = $to.after(d) | ||
while (d > 1 && after == $to.end(--d)) ++after | ||
let fittedAfter = fitRight($from, doc.resolve(after), fittedLeft) | ||
if (fittedAfter) | ||
return new ReplaceAroundStep(from, after, to, $to.end(), fittedAfter, fittedLeft.size) | ||
} | ||
return fitted.size || from != to ? new ReplaceStep(from, to, fitted) : null | ||
return new Fitter($from, $to, slice).fit() | ||
} | ||
@@ -62,122 +50,2 @@ | ||
function fitLeftInner($from, depth, placed, placedBelow) { | ||
let content = Fragment.empty, openEnd = 0, placedHere = placed[depth] | ||
if ($from.depth > depth) { | ||
let inner = fitLeftInner($from, depth + 1, placed, placedBelow || placedHere) | ||
openEnd = inner.openEnd + 1 | ||
content = Fragment.from($from.node(depth + 1).copy(inner.content)) | ||
} | ||
if (placedHere) { | ||
content = content.append(placedHere.content) | ||
openEnd = placedHere.openEnd | ||
} | ||
if (placedBelow) { | ||
content = content.append($from.node(depth).contentMatchAt($from.indexAfter(depth)).fillBefore(Fragment.empty, true)) | ||
openEnd = 0 | ||
} | ||
return {content, openEnd} | ||
} | ||
function fitLeft($from, placed) { | ||
let {content, openEnd} = fitLeftInner($from, 0, placed, false) | ||
return new Slice(content, $from.depth, openEnd || 0) | ||
} | ||
function fitRightJoin(content, parent, $from, $to, depth, openStart, openEnd) { | ||
let match, count = content.childCount, matchCount = count - (openEnd > 0 ? 1 : 0) | ||
let parentNode = openStart < 0 ? parent : $from.node(depth) | ||
if (openStart < 0) | ||
match = parentNode.contentMatchAt(matchCount) | ||
else if (count == 1 && openEnd > 0) | ||
match = parentNode.contentMatchAt(openStart ? $from.index(depth) : $from.indexAfter(depth)) | ||
else | ||
match = parentNode.contentMatchAt($from.indexAfter(depth)) | ||
.matchFragment(content, count > 0 && openStart ? 1 : 0, matchCount) | ||
let toNode = $to.node(depth) | ||
if (openEnd > 0 && depth < $to.depth) { | ||
let after = toNode.content.cutByIndex($to.indexAfter(depth)).addToStart(content.lastChild) | ||
let joinable = match.fillBefore(after, true) | ||
// Can't insert content if there's a single node stretched across this gap | ||
if (joinable && joinable.size && openStart > 0 && count == 1) joinable = null | ||
if (joinable) { | ||
let inner = fitRightJoin(content.lastChild.content, content.lastChild, $from, $to, | ||
depth + 1, count == 1 ? openStart - 1 : -1, openEnd - 1) | ||
if (inner) { | ||
let last = content.lastChild.copy(inner) | ||
if (joinable.size) | ||
return content.cutByIndex(0, count - 1).append(joinable).addToEnd(last) | ||
else | ||
return content.replaceChild(count - 1, last) | ||
} | ||
} | ||
} | ||
if (openEnd > 0) | ||
match = match.matchType((count == 1 && openStart > 0 ? $from.node(depth + 1) : content.lastChild).type) | ||
// If we're here, the next level can't be joined, so we see what | ||
// happens if we leave it open. | ||
let toIndex = $to.index(depth) | ||
if (toIndex == toNode.childCount && !toNode.type.compatibleContent(parent.type)) return null | ||
let joinable = match.fillBefore(toNode.content, true, toIndex) | ||
for (let i = toIndex; joinable && i < toNode.content.childCount; i++) | ||
if (!parentNode.type.allowsMarks(toNode.content.child(i).marks)) joinable = null | ||
if (!joinable) return null | ||
if (openEnd > 0) { | ||
let closed = fitRightClosed(content.lastChild, openEnd - 1, $from, depth + 1, | ||
count == 1 ? openStart - 1 : -1) | ||
content = content.replaceChild(count - 1, closed) | ||
} | ||
content = content.append(joinable) | ||
if ($to.depth > depth) | ||
content = content.addToEnd(fitRightSeparate($to, depth + 1)) | ||
return content | ||
} | ||
function fitRightClosed(node, openEnd, $from, depth, openStart) { | ||
let match, content = node.content, count = content.childCount | ||
if (openStart >= 0) | ||
match = $from.node(depth).contentMatchAt($from.indexAfter(depth)) | ||
.matchFragment(content, openStart > 0 ? 1 : 0, count) | ||
else | ||
match = node.contentMatchAt(count) | ||
if (openEnd > 0) { | ||
let closed = fitRightClosed(content.lastChild, openEnd - 1, $from, depth + 1, | ||
count == 1 ? openStart - 1 : -1) | ||
content = content.replaceChild(count - 1, closed) | ||
} | ||
return node.copy(content.append(match.fillBefore(Fragment.empty, true))) | ||
} | ||
function fitRightSeparate($to, depth) { | ||
let node = $to.node(depth) | ||
let fill = node.contentMatchAt(0).fillBefore(node.content, true, $to.index(depth)) | ||
if ($to.depth > depth) fill = fill.addToEnd(fitRightSeparate($to, depth + 1)) | ||
return node.copy(fill) | ||
} | ||
function normalizeSlice(content, openStart, openEnd) { | ||
while (openStart > 0 && openEnd > 0 && content.childCount == 1) { | ||
content = content.firstChild.content | ||
openStart-- | ||
openEnd-- | ||
} | ||
return new Slice(content, openStart, openEnd) | ||
} | ||
// : (ResolvedPos, ResolvedPos, number, Slice) → Slice | ||
function fitRight($from, $to, slice) { | ||
let fitted = fitRightJoin(slice.content, $from.node(0), $from, $to, 0, slice.openStart, slice.openEnd) | ||
if (!fitted) return null | ||
return normalizeSlice(fitted, slice.openStart, $to.depth) | ||
} | ||
function fitsTrivially($from, $to, slice) { | ||
@@ -188,26 +56,2 @@ return !slice.openStart && !slice.openEnd && $from.start() == $to.start() && | ||
function canMoveText($from, $to, slice) { | ||
if (!$to.parent.isTextblock) return false | ||
let parent = slice.openEnd ? nodeRight(slice.content, slice.openEnd) | ||
: $from.node($from.depth - (slice.openStart - slice.openEnd)) | ||
if (!parent.isTextblock) return false | ||
for (let i = $to.index(); i < $to.parent.childCount; i++) | ||
if (!parent.type.allowsMarks($to.parent.child(i).marks)) return false | ||
let match | ||
if (slice.openEnd) { | ||
match = parent.contentMatchAt(parent.childCount) | ||
} else { | ||
match = parent.contentMatchAt(parent.childCount) | ||
if (slice.size) match = match.matchFragment(slice.content, slice.openStart ? 1 : 0) | ||
} | ||
match = match.matchFragment($to.parent.content, $to.index()) | ||
return match && match.validEnd | ||
} | ||
function nodeRight(content, depth) { | ||
for (let i = 1; i < depth; i++) content = content.lastChild.content | ||
return content.lastChild | ||
} | ||
// Algorithm for 'placing' the elements of a slice into a gap: | ||
@@ -220,182 +64,274 @@ // | ||
// | ||
// So placeSlice splits up a slice into a number of sub-slices, | ||
// along with information on where they can be placed on the given | ||
// left-side edge. It works by walking the open side of the slice, | ||
// from the inside out, and trying to find a landing spot for each | ||
// element, by simultaneously scanning over the gap side. When no | ||
// place is found for an open node's content, it is left in that node. | ||
// This class tracks the state of the placement progress in the | ||
// following properties: | ||
// | ||
// - `frontier` holds a stack of `{type, match}` objects that | ||
// represent the open side of the replacement. It starts at | ||
// `$from`, then moves forward as content is placed, and is finally | ||
// reconciled with `$to`. | ||
// | ||
// - `unplaced` is a slice that represents the content that hasn't | ||
// been placed yet. | ||
// | ||
// - `placed` is a fragment of placed content. Its open-start value | ||
// is implicit in `$from`, and its open-end value in `frontier`. | ||
class Fitter { | ||
constructor($from, $to, slice) { | ||
this.$to = $to | ||
this.$from = $from | ||
this.unplaced = slice | ||
// : (ResolvedPos, Slice) → [{content: Fragment, openEnd: number, depth: number}] | ||
function placeSlice($from, slice) { | ||
let frontier = new Frontier($from) | ||
for (let pass = 1; slice.size && pass <= 3; pass++) { | ||
let value = frontier.placeSlice(slice.content, slice.openStart, slice.openEnd, pass) | ||
if (pass == 3 && value != slice && value.size) pass = 0 // Restart if the 3rd pass made progress but left content | ||
slice = value | ||
this.frontier = [] | ||
for (let i = 0; i <= $from.depth; i++) { | ||
let node = $from.node(i) | ||
this.frontier.push({ | ||
type: node.type, | ||
match: node.contentMatchAt($from.indexAfter(i)) | ||
}) | ||
} | ||
this.placed = Fragment.empty | ||
for (let i = $from.depth; i > 0; i--) | ||
this.placed = Fragment.from($from.node(i).copy(this.placed)) | ||
} | ||
while (frontier.open.length) frontier.closeNode() | ||
return frontier.placed | ||
} | ||
// Helper class that models the open side of the insert position, | ||
// keeping track of the content match and already inserted content | ||
// at each depth. | ||
class Frontier { | ||
constructor($pos) { | ||
// : [{parent: Node, match: ContentMatch, content: Fragment, wrapper: bool, openEnd: number, depth: number}] | ||
this.open = [] | ||
for (let d = 0; d <= $pos.depth; d++) { | ||
let parent = $pos.node(d), match = parent.contentMatchAt($pos.indexAfter(d)) | ||
this.open.push({parent, match, content: Fragment.empty, wrapper: false, openEnd: 0, depth: d}) | ||
get depth() { return this.frontier.length - 1 } | ||
fit() { | ||
// As long as there's unplaced content, try to place some of it. | ||
// If that fails, either increase the open score of the unplaced | ||
// slice, or drop nodes from it, and then try again. | ||
while (this.unplaced.size) { | ||
let fit = this.findFittable() | ||
if (fit) this.placeNodes(fit) | ||
else this.openMore() || this.dropNode() | ||
} | ||
this.placed = [] | ||
// When there's inline content directly after the frontier _and_ | ||
// directly after `this.$to`, we must generate a `ReplaceAround` | ||
// step that pulls that content into the node after the frontier. | ||
// That means the fitting must be done to the end of the textblock | ||
// node after `this.$to`, not `this.$to` itself. | ||
let moveInline = this.mustMoveInline(), placedSize = this.placed.size - this.depth - this.$from.depth | ||
let $from = this.$from, $to = moveInline < 0 ? this.$to : $from.doc.resolve(moveInline) | ||
if (this.close($to)) { // If closing to `$to` succeeded, create a step | ||
let content = this.placed, openStart = $from.depth, openEnd = $to.depth | ||
while (openStart && openEnd && content.childCount == 1) { // Normalize by dropping open parent nodes | ||
content = content.firstChild.content | ||
openStart--; openEnd-- | ||
} | ||
let slice = new Slice(content, openStart, openEnd) | ||
if (moveInline > -1) | ||
return new ReplaceAroundStep($from.pos, moveInline, this.$to.pos, this.$to.end(), slice, placedSize) | ||
if (slice.size || $from.pos != this.$to.pos) // Don't generate no-op steps | ||
return new ReplaceStep($from.pos, $to.pos, slice) | ||
} | ||
return null | ||
} | ||
// : (Fragment, number, number, number, ?Node) → Slice | ||
// Tries to place the content of the given slice, and returns a | ||
// slice containing unplaced content. | ||
// | ||
// pass 1: try to fit directly | ||
// pass 2: allow wrapper nodes to be introduced | ||
// pass 3: allow unwrapping of nodes that aren't open | ||
placeSlice(fragment, openStart, openEnd, pass, parent) { | ||
if (openStart > 0) { | ||
let first = fragment.firstChild | ||
let inner = this.placeSlice(first.content, Math.max(0, openStart - 1), | ||
openEnd && fragment.childCount == 1 ? openEnd - 1 : 0, | ||
pass, first) | ||
if (inner.content != first.content) { | ||
if (inner.content.size) { | ||
fragment = fragment.replaceChild(0, first.copy(inner.content)) | ||
openStart = inner.openStart + 1 | ||
// Find a position on the start spine of `this.unplaced` that has | ||
// content that can be moved somewhere on the frontier. Returns two | ||
// depths, one for the slice and one for the frontier. | ||
findFittable() { | ||
// Only try wrapping nodes (pass 2) after finding a place without | ||
// wrapping failed. | ||
for (let pass = 1; pass <= 2; pass++) { | ||
for (let sliceDepth = this.unplaced.openStart; sliceDepth >= 0; sliceDepth--) { | ||
let fragment, parent | ||
if (sliceDepth) { | ||
parent = contentAt(this.unplaced.content, sliceDepth - 1).firstChild | ||
fragment = parent.content | ||
} else { | ||
if (fragment.childCount == 1) openEnd = 0 | ||
fragment = fragment.cutByIndex(1) | ||
openStart = 0 | ||
fragment = this.unplaced.content | ||
} | ||
let first = fragment.firstChild | ||
for (let frontierDepth = this.depth; frontierDepth >= 0; frontierDepth--) { | ||
let {type, match} = this.frontier[frontierDepth], wrap, inject | ||
// In pass 1, if the next node matches, or there is no next | ||
// node but the parents look compatible, we've found a | ||
// place. | ||
if (pass == 1 && (first ? match.matchType(first.type) || (inject = match.fillBefore(Fragment.from(first), false)) | ||
: type.compatibleContent(parent.type))) | ||
return {sliceDepth, frontierDepth, parent, inject} | ||
// In pass 2, look for a set of wrapping nodes that make | ||
// `first` fit here. | ||
else if (pass == 2 && first && (wrap = match.findWrapping(first.type))) | ||
return {sliceDepth, frontierDepth, parent, wrap} | ||
// Don't continue looking further up if the parent node | ||
// would fit here. | ||
if (parent && match.matchType(parent.type)) break | ||
} | ||
} | ||
} | ||
let result = this.placeContent(fragment, openStart, openEnd, pass, parent) | ||
if (pass > 2 && result.size && openStart == 0) { | ||
let child = result.content.firstChild, single = result.content.childCount == 1 | ||
this.placeContent(child.content, 0, openEnd && single ? openEnd - 1 : 0, pass, child) | ||
result = single ? Fragment.empty : new Slice(result.content.cutByIndex(1), 0, openEnd) | ||
} | ||
return result | ||
} | ||
placeContent(fragment, openStart, openEnd, pass, parent) { | ||
let i = 0 | ||
// Go over the fragment's children | ||
for (; i < fragment.childCount; i++) { | ||
let child = fragment.child(i), placed = false, last = i == fragment.childCount - 1 | ||
// Try each open node in turn, starting from the innermost | ||
for (let d = this.open.length - 1; d >= 0; d--) { | ||
let open = this.open[d], wrap | ||
openMore() { | ||
let {content, openStart, openEnd} = this.unplaced | ||
let inner = contentAt(content, openStart) | ||
if (!inner.childCount || inner.firstChild.isLeaf) return false | ||
this.unplaced = new Slice(content, openStart + 1, | ||
Math.max(openEnd, inner.size + openStart >= content.size - openEnd ? openStart + 1 : 0)) | ||
return true | ||
} | ||
// If pass > 1, it is allowed to wrap the node to help find a | ||
// fit, so if findWrapping returns something, we add open | ||
// nodes to the frontier for that wrapping. | ||
if (pass > 1 && (wrap = open.match.findWrapping(child.type)) && | ||
!(parent && wrap.length && wrap[wrap.length - 1] == parent.type)) { | ||
while (this.open.length - 1 > d) this.closeNode() | ||
for (let w = 0; w < wrap.length; w++) { | ||
open.match = open.match.matchType(wrap[w]) | ||
d++ | ||
open = {parent: wrap[w].create(), | ||
match: wrap[w].contentMatch, | ||
content: Fragment.empty, wrapper: true, openEnd: 0, depth: d + w} | ||
this.open.push(open) | ||
} | ||
} | ||
dropNode() { | ||
let {content, openStart, openEnd} = this.unplaced | ||
let inner = contentAt(content, openStart) | ||
if (inner.childCount <= 1 && openStart > 0) { | ||
let openAtEnd = content.size - openStart <= openStart + inner.size | ||
this.unplaced = new Slice(dropFromFragment(content, openStart - 1, 1), openStart - 1, | ||
openAtEnd ? openStart - 1 : openEnd) | ||
} else { | ||
this.unplaced = new Slice(dropFromFragment(content, openStart, 1), openStart, openEnd) | ||
} | ||
} | ||
// See if the child fits here | ||
let match = open.match.matchType(child.type) | ||
if (!match) { | ||
let fill = open.match.fillBefore(Fragment.from(child)) | ||
if (fill) { | ||
for (let j = 0; j < fill.childCount; j++) { | ||
let ch = fill.child(j) | ||
this.addNode(open, ch, 0) | ||
match = open.match.matchFragment(ch) | ||
} | ||
} else if (parent && open.match.matchType(parent.type)) { | ||
// Don't continue looking further up if the parent node | ||
// would fit here. | ||
break | ||
} else { | ||
continue | ||
} | ||
} | ||
// : ({sliceDepth: number, frontierDepth: number, parent: ?Node, wrap: ?[NodeType], inject: ?Fragment}) | ||
// Move content from the unplaced slice at `sliceDepth` to the | ||
// frontier node at `frontierDepth`. Close that frontier node when | ||
// applicable. | ||
placeNodes({sliceDepth, frontierDepth, parent, inject, wrap}) { | ||
while (this.depth > frontierDepth) this.closeFrontierNode() | ||
if (wrap) for (let i = 0; i < wrap.length; i++) this.openFrontierNode(wrap[i]) | ||
// Close open nodes above this one, since we're starting to | ||
// add to this. | ||
while (this.open.length - 1 > d) this.closeNode() | ||
// Strip marks from the child or close its start when necessary | ||
child = child.mark(open.parent.type.allowedMarks(child.marks)) | ||
if (openStart) { | ||
child = closeNodeStart(child, openStart, last ? openEnd : 0) | ||
openStart = 0 | ||
} | ||
// Add the child to this open node and adjust its metadata | ||
this.addNode(open, child, last ? openEnd : 0) | ||
open.match = match | ||
if (last) openEnd = 0 | ||
placed = true | ||
break | ||
let slice = this.unplaced, fragment = parent ? parent.content : slice.content | ||
let openStart = slice.openStart - sliceDepth | ||
let taken = 0, add = [] | ||
let {match, type} = this.frontier[frontierDepth] | ||
if (inject) { | ||
for (let i = 0; i < inject.childCount; i++) add.push(inject.child(i)) | ||
match = match.matchFragment(inject) | ||
} | ||
// Computes the amount of (end) open nodes at the end of the | ||
// fragment. When 0, the parent is open, but no more. When | ||
// negative, nothing is open. | ||
let openEndCount = (fragment.size + sliceDepth) - (slice.content.size - slice.openEnd) | ||
// Scan over the fragment, fitting as many child nodes as | ||
// possible. | ||
while (taken < fragment.childCount) { | ||
let next = fragment.child(taken), matches = match.matchType(next.type) | ||
if (!matches) break | ||
taken++ | ||
if (taken > 1 || openStart == 0 || next.content.size) { // Drop empty open nodes | ||
match = matches | ||
add.push(closeNodeStart(next.mark(type.allowedMarks(next.marks)), taken == 1 ? openStart : 0, | ||
taken == fragment.childCount ? openEndCount : -1)) | ||
} | ||
// As soon as we've failed to place a node we stop looking at | ||
// later nodes | ||
if (!placed) break | ||
} | ||
// Close the current open node if it's not the the root and we | ||
// either placed up to the end of the node or the the current | ||
// slice depth's node type matches the open node's type | ||
if (this.open.length > 1 && | ||
(i > 0 && i == fragment.childCount || | ||
parent && this.open[this.open.length - 1].parent.type == parent.type)) | ||
this.closeNode() | ||
let toEnd = taken == fragment.childCount | ||
if (!toEnd) openEndCount = -1 | ||
return new Slice(fragment.cutByIndex(i), openStart, openEnd) | ||
// If the parent types match, and the entire node was moved, and | ||
// it's not open, close this frontier node right away. | ||
if (toEnd && openEndCount < 0 && parent && parent.type == this.frontier[this.depth].type) this.closeFrontierNode() | ||
else this.frontier[frontierDepth].match = match | ||
// Add new frontier nodes for any open nodes at the end. | ||
for (let i = 0, cur = fragment; i < openEndCount; i++) { | ||
let node = cur.lastChild | ||
this.frontier.push({type: node.type, match: node.contentMatchAt(node.childCount)}) | ||
cur = node.content | ||
} | ||
// Update `this.unplaced`. Drop the entire node from which we | ||
// placed it we got to its end, otherwise just drop the placed | ||
// nodes. | ||
this.unplaced = !toEnd ? new Slice(dropFromFragment(slice.content, sliceDepth, taken), slice.openStart, slice.openEnd) | ||
: sliceDepth == 0 ? Slice.empty | ||
: new Slice(dropFromFragment(slice.content, sliceDepth - 1, 1), | ||
sliceDepth - 1, openEndCount < 0 ? slice.openEnd : sliceDepth - 1) | ||
this.placed = addToFragment(this.placed, frontierDepth, Fragment.from(add)) | ||
} | ||
addNode(open, node, openEnd) { | ||
open.content = closeFragmentEnd(open.content, open.openEnd).addToEnd(node) | ||
open.openEnd = openEnd | ||
mustMoveInline() { | ||
if (!this.$to.parent.isTextblock) return -1 | ||
let top = this.frontier[this.depth], level | ||
if (!top.type.isTextblock || !contentAfterFits(this.$to, this.$to.depth, top.type, top.match, false) || | ||
(this.$to.depth == this.depth && (level = this.findCloseLevel(this.$to)) && level.depth == this.depth)) return -1 | ||
let {depth} = this.$to, after = this.$to.after(depth) | ||
while (depth > 1 && after == this.$to.end(--depth)) ++after | ||
return after | ||
} | ||
closeNode() { | ||
let open = this.open.pop() | ||
if (open.content.size == 0) { | ||
// Nothing here | ||
} else if (open.wrapper) { | ||
this.addNode(this.open[this.open.length - 1], open.parent.copy(open.content), open.openEnd + 1) | ||
} else { | ||
this.placed[open.depth] = {depth: open.depth, content: open.content, openEnd: open.openEnd} | ||
findCloseLevel($to) { | ||
scan: for (let i = Math.min(this.depth, $to.depth); i >= 0; i--) { | ||
let {match, type} = this.frontier[i] | ||
let fit = contentAfterFits($to, i, type, match, false) | ||
if (!fit) continue | ||
for (let d = i - 1; d >= 0; d--) { | ||
let {match, type} = this.frontier[d] | ||
let matches = contentAfterFits($to, d, type, match, true) | ||
if (!matches || matches.childCount) continue scan | ||
} | ||
return {depth: i, fit} | ||
} | ||
} | ||
close($to) { | ||
let close = this.findCloseLevel($to) | ||
if (!close) return false | ||
while (this.depth > close.depth) this.closeFrontierNode() | ||
if (close.fit.childCount) this.placed = addToFragment(this.placed, close.depth, close.fit) | ||
for (let d = close.depth + 1; d <= $to.depth; d++) { | ||
let node = $to.node(d), add = node.type.contentMatch.fillBefore(node.content, true, $to.index(d)) | ||
this.openFrontierNode(node.type, add) | ||
} | ||
return true | ||
} | ||
openFrontierNode(type, content) { | ||
let top = this.frontier[this.depth] | ||
if (top.type == type && type.name == "list_item") throw new Error("NOOOP") | ||
top.match = top.match.matchType(type) | ||
this.placed = addToFragment(this.placed, this.depth, Fragment.from(type.create(null, content))) | ||
this.frontier.push({type, match: type.contentMatch}) | ||
} | ||
closeFrontierNode() { | ||
let open = this.frontier.pop() | ||
let add = open.match.fillBefore(Fragment.empty, true) | ||
if (add.childCount) this.placed = addToFragment(this.placed, this.frontier.length, add) | ||
} | ||
} | ||
function dropFromFragment(fragment, depth, count) { | ||
if (depth == 0) return fragment.cutByIndex(count) | ||
return fragment.replaceChild(0, fragment.firstChild.copy(dropFromFragment(fragment.firstChild.content, depth - 1, count))) | ||
} | ||
function addToFragment(fragment, depth, content) { | ||
if (depth == 0) return fragment.append(content) | ||
return fragment.replaceChild(fragment.childCount - 1, | ||
fragment.lastChild.copy(addToFragment(fragment.lastChild.content, depth - 1, content))) | ||
} | ||
function contentAt(fragment, depth) { | ||
for (let i = 0; i < depth; i++) fragment = fragment.firstChild.content | ||
return fragment | ||
} | ||
function closeNodeStart(node, openStart, openEnd) { | ||
let content = node.content | ||
if (openStart > 1) { | ||
let first = closeNodeStart(node.firstChild, openStart - 1, node.childCount == 1 ? openEnd - 1 : 0) | ||
content = node.content.replaceChild(0, first) | ||
if (openStart <= 0) return node | ||
let frag = node.content | ||
if (openStart > 1) | ||
frag = frag.replaceChild(0, closeNodeStart(frag.firstChild, openStart - 1, frag.childCount == 1 ? openEnd - 1 : 0)) | ||
if (openStart > 0) { | ||
frag = node.type.contentMatch.fillBefore(frag).append(frag) | ||
if (openEnd <= 0) frag = frag.append(node.type.contentMatch.matchFragment(frag).fillBefore(Fragment.empty, true)) | ||
} | ||
let fill = node.type.contentMatch.fillBefore(content, openEnd == 0) | ||
return node.copy(fill.append(content)) | ||
return node.copy(frag) | ||
} | ||
function closeNodeEnd(node, depth) { | ||
let content = node.content | ||
if (depth > 1) { | ||
let last = closeNodeEnd(node.lastChild, depth - 1) | ||
content = node.content.replaceChild(node.childCount - 1, last) | ||
} | ||
let fill = node.contentMatchAt(node.childCount).fillBefore(Fragment.empty, true) | ||
return node.copy(content.append(fill)) | ||
function contentAfterFits($to, depth, type, match, open) { | ||
let node = $to.node(depth), index = open ? $to.indexAfter(depth) : $to.index(depth) | ||
if (index == node.childCount && !type.compatibleContent(node.type)) return null | ||
let fit = match.fillBefore(node.content, true, index) | ||
return fit && !invalidMarks(type, node.content, index) ? fit : null | ||
} | ||
function closeFragmentEnd(fragment, depth) { | ||
return depth ? fragment.replaceChild(fragment.childCount - 1, closeNodeEnd(fragment.lastChild, depth)) : fragment | ||
function invalidMarks(type, fragment, start) { | ||
for (let i = start; i < fragment.childCount; i++) | ||
if (!type.allowsMarks(fragment.child(i).marks)) return true | ||
return false | ||
} | ||
@@ -555,1 +491,2 @@ | ||
} | ||
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
473654
4391
2