Initial
This commit is contained in:
59
resources/app/node_modules/prosemirror-transform/src/README.md
generated
vendored
Normal file
59
resources/app/node_modules/prosemirror-transform/src/README.md
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
This module defines a way of modifying documents that allows changes
|
||||
to be recorded, replayed, and reordered. You can read more about
|
||||
transformations in [the guide](/docs/guide/#transform).
|
||||
|
||||
### Steps
|
||||
|
||||
Transforming happens in `Step`s, which are atomic, well-defined
|
||||
modifications to a document. [Applying](#transform.Step.apply) a step
|
||||
produces a new document.
|
||||
|
||||
Each step provides a [change map](#transform.StepMap) that maps
|
||||
positions in the old document to position in the transformed document.
|
||||
Steps can be [inverted](#transform.Step.invert) to create a step that
|
||||
undoes their effect, and chained together in a convenience object
|
||||
called a [`Transform`](#transform.Transform).
|
||||
|
||||
@Step
|
||||
@StepResult
|
||||
@ReplaceStep
|
||||
@ReplaceAroundStep
|
||||
@AddMarkStep
|
||||
@RemoveMarkStep
|
||||
@AddNodeMarkStep
|
||||
@RemoveNodeMarkStep
|
||||
@AttrStep
|
||||
@DocAttrStep
|
||||
|
||||
### Position Mapping
|
||||
|
||||
Mapping positions from one document to another by running through the
|
||||
[step maps](#transform.StepMap) produced by steps is an important
|
||||
operation in ProseMirror. It is used, for example, for updating the
|
||||
selection when the document changes.
|
||||
|
||||
@Mappable
|
||||
@MapResult
|
||||
@StepMap
|
||||
@Mapping
|
||||
|
||||
### Document transforms
|
||||
|
||||
Because you often need to collect a number of steps together to effect
|
||||
a composite change, ProseMirror provides an abstraction to make this
|
||||
easy. [State transactions](#state.Transaction) are a subclass of
|
||||
transforms.
|
||||
|
||||
@Transform
|
||||
|
||||
The following helper functions can be useful when creating
|
||||
transformations or determining whether they are even possible.
|
||||
|
||||
@replaceStep
|
||||
@liftTarget
|
||||
@findWrapping
|
||||
@canSplit
|
||||
@canJoin
|
||||
@joinPoint
|
||||
@insertPoint
|
||||
@dropPoint
|
||||
98
resources/app/node_modules/prosemirror-transform/src/attr_step.ts
generated
vendored
Normal file
98
resources/app/node_modules/prosemirror-transform/src/attr_step.ts
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
import {Fragment, Slice, Node, Schema} from "prosemirror-model"
|
||||
import {Step, StepResult} from "./step"
|
||||
import {StepMap, Mappable} from "./map"
|
||||
|
||||
/// Update an attribute in a specific node.
|
||||
export class AttrStep extends Step {
|
||||
/// Construct an attribute step.
|
||||
constructor(
|
||||
/// The position of the target node.
|
||||
readonly pos: number,
|
||||
/// The attribute to set.
|
||||
readonly attr: string,
|
||||
// The attribute's new value.
|
||||
readonly value: any
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
apply(doc: Node) {
|
||||
let node = doc.nodeAt(this.pos)
|
||||
if (!node) return StepResult.fail("No node at attribute step's position")
|
||||
let attrs = Object.create(null)
|
||||
for (let name in node.attrs) attrs[name] = node.attrs[name]
|
||||
attrs[this.attr] = this.value
|
||||
let updated = node.type.create(attrs, null, node.marks)
|
||||
return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1))
|
||||
}
|
||||
|
||||
getMap() {
|
||||
return StepMap.empty
|
||||
}
|
||||
|
||||
invert(doc: Node) {
|
||||
return new AttrStep(this.pos, this.attr, doc.nodeAt(this.pos)!.attrs[this.attr])
|
||||
}
|
||||
|
||||
map(mapping: Mappable) {
|
||||
let pos = mapping.mapResult(this.pos, 1)
|
||||
return pos.deletedAfter ? null : new AttrStep(pos.pos, this.attr, this.value)
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
return {stepType: "attr", pos: this.pos, attr: this.attr, value: this.value}
|
||||
}
|
||||
|
||||
static fromJSON(schema: Schema, json: any) {
|
||||
if (typeof json.pos != "number" || typeof json.attr != "string")
|
||||
throw new RangeError("Invalid input for AttrStep.fromJSON")
|
||||
return new AttrStep(json.pos, json.attr, json.value)
|
||||
}
|
||||
}
|
||||
|
||||
Step.jsonID("attr", AttrStep)
|
||||
|
||||
/// Update an attribute in the doc node.
|
||||
export class DocAttrStep extends Step {
|
||||
/// Construct an attribute step.
|
||||
constructor(
|
||||
/// The attribute to set.
|
||||
readonly attr: string,
|
||||
// The attribute's new value.
|
||||
readonly value: any
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
apply(doc: Node) {
|
||||
let attrs = Object.create(null)
|
||||
for (let name in doc.attrs) attrs[name] = doc.attrs[name]
|
||||
attrs[this.attr] = this.value
|
||||
let updated = doc.type.create(attrs, doc.content, doc.marks)
|
||||
return StepResult.ok(updated)
|
||||
}
|
||||
|
||||
getMap() {
|
||||
return StepMap.empty
|
||||
}
|
||||
|
||||
invert(doc: Node) {
|
||||
return new DocAttrStep(this.attr, doc.attrs[this.attr])
|
||||
}
|
||||
|
||||
map(mapping: Mappable) {
|
||||
return this
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
return {stepType: "docAttr", attr: this.attr, value: this.value}
|
||||
}
|
||||
|
||||
static fromJSON(schema: Schema, json: any) {
|
||||
if (typeof json.attr != "string")
|
||||
throw new RangeError("Invalid input for DocAttrStep.fromJSON")
|
||||
return new DocAttrStep(json.attr, json.value)
|
||||
}
|
||||
}
|
||||
|
||||
Step.jsonID("docAttr", DocAttrStep)
|
||||
11
resources/app/node_modules/prosemirror-transform/src/index.ts
generated
vendored
Normal file
11
resources/app/node_modules/prosemirror-transform/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
export {Transform} from "./transform"
|
||||
/// @internal
|
||||
export {TransformError} from "./transform"
|
||||
export {Step, StepResult} from "./step"
|
||||
export {joinPoint, canJoin, canSplit, insertPoint, dropPoint, liftTarget, findWrapping} from "./structure"
|
||||
export {StepMap, MapResult, Mapping, Mappable} from "./map"
|
||||
export {AddMarkStep, RemoveMarkStep, AddNodeMarkStep, RemoveNodeMarkStep} from "./mark_step"
|
||||
export {ReplaceStep, ReplaceAroundStep} from "./replace_step"
|
||||
export {AttrStep, DocAttrStep} from "./attr_step"
|
||||
import "./mark"
|
||||
export {replaceStep} from "./replace"
|
||||
275
resources/app/node_modules/prosemirror-transform/src/map.ts
generated
vendored
Normal file
275
resources/app/node_modules/prosemirror-transform/src/map.ts
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
/// There are several things that positions can be mapped through.
|
||||
/// Such objects conform to this interface.
|
||||
export interface Mappable {
|
||||
/// Map a position through this object. When given, `assoc` (should
|
||||
/// be -1 or 1, defaults to 1) determines with which side the
|
||||
/// position is associated, which determines in which direction to
|
||||
/// move when a chunk of content is inserted at the mapped position.
|
||||
map: (pos: number, assoc?: number) => number
|
||||
|
||||
/// Map a position, and return an object containing additional
|
||||
/// information about the mapping. The result's `deleted` field tells
|
||||
/// you whether the position was deleted (completely enclosed in a
|
||||
/// replaced range) during the mapping. When content on only one side
|
||||
/// is deleted, the position itself is only considered deleted when
|
||||
/// `assoc` points in the direction of the deleted content.
|
||||
mapResult: (pos: number, assoc?: number) => MapResult
|
||||
}
|
||||
|
||||
// Recovery values encode a range index and an offset. They are
|
||||
// represented as numbers, because tons of them will be created when
|
||||
// mapping, for example, a large number of decorations. The number's
|
||||
// lower 16 bits provide the index, the remaining bits the offset.
|
||||
//
|
||||
// Note: We intentionally don't use bit shift operators to en- and
|
||||
// decode these, since those clip to 32 bits, which we might in rare
|
||||
// cases want to overflow. A 64-bit float can represent 48-bit
|
||||
// integers precisely.
|
||||
|
||||
const lower16 = 0xffff
|
||||
const factor16 = Math.pow(2, 16)
|
||||
|
||||
function makeRecover(index: number, offset: number) { return index + offset * factor16 }
|
||||
function recoverIndex(value: number) { return value & lower16 }
|
||||
function recoverOffset(value: number) { return (value - (value & lower16)) / factor16 }
|
||||
|
||||
const DEL_BEFORE = 1, DEL_AFTER = 2, DEL_ACROSS = 4, DEL_SIDE = 8
|
||||
|
||||
/// An object representing a mapped position with extra
|
||||
/// information.
|
||||
export class MapResult {
|
||||
/// @internal
|
||||
constructor(
|
||||
/// The mapped version of the position.
|
||||
readonly pos: number,
|
||||
/// @internal
|
||||
readonly delInfo: number,
|
||||
/// @internal
|
||||
readonly recover: number | null
|
||||
) {}
|
||||
|
||||
/// Tells you whether the position was deleted, that is, whether the
|
||||
/// step removed the token on the side queried (via the `assoc`)
|
||||
/// argument from the document.
|
||||
get deleted() { return (this.delInfo & DEL_SIDE) > 0 }
|
||||
|
||||
/// Tells you whether the token before the mapped position was deleted.
|
||||
get deletedBefore() { return (this.delInfo & (DEL_BEFORE | DEL_ACROSS)) > 0 }
|
||||
|
||||
/// True when the token after the mapped position was deleted.
|
||||
get deletedAfter() { return (this.delInfo & (DEL_AFTER | DEL_ACROSS)) > 0 }
|
||||
|
||||
/// Tells whether any of the steps mapped through deletes across the
|
||||
/// position (including both the token before and after the
|
||||
/// position).
|
||||
get deletedAcross() { return (this.delInfo & DEL_ACROSS) > 0 }
|
||||
}
|
||||
|
||||
/// A map describing the deletions and insertions made by a step, which
|
||||
/// can be used to find the correspondence between positions in the
|
||||
/// pre-step version of a document and the same position in the
|
||||
/// post-step version.
|
||||
export class StepMap implements Mappable {
|
||||
/// Create a position map. The modifications to the document are
|
||||
/// represented as an array of numbers, in which each group of three
|
||||
/// represents a modified chunk as `[start, oldSize, newSize]`.
|
||||
constructor(
|
||||
/// @internal
|
||||
readonly ranges: readonly number[],
|
||||
/// @internal
|
||||
readonly inverted = false
|
||||
) {
|
||||
if (!ranges.length && StepMap.empty) return StepMap.empty
|
||||
}
|
||||
|
||||
/// @internal
|
||||
recover(value: number) {
|
||||
let diff = 0, index = recoverIndex(value)
|
||||
if (!this.inverted) for (let i = 0; i < index; i++)
|
||||
diff += this.ranges[i * 3 + 2] - this.ranges[i * 3 + 1]
|
||||
return this.ranges[index * 3] + diff + recoverOffset(value)
|
||||
}
|
||||
|
||||
mapResult(pos: number, assoc = 1): MapResult { return this._map(pos, assoc, false) as MapResult }
|
||||
|
||||
map(pos: number, assoc = 1): number { return this._map(pos, assoc, true) as number }
|
||||
|
||||
/// @internal
|
||||
_map(pos: number, assoc: number, simple: boolean) {
|
||||
let diff = 0, oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2
|
||||
for (let i = 0; i < this.ranges.length; i += 3) {
|
||||
let start = this.ranges[i] - (this.inverted ? diff : 0)
|
||||
if (start > pos) break
|
||||
let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex], end = start + oldSize
|
||||
if (pos <= end) {
|
||||
let side = !oldSize ? assoc : pos == start ? -1 : pos == end ? 1 : assoc
|
||||
let result = start + diff + (side < 0 ? 0 : newSize)
|
||||
if (simple) return result
|
||||
let recover = pos == (assoc < 0 ? start : end) ? null : makeRecover(i / 3, pos - start)
|
||||
let del = pos == start ? DEL_AFTER : pos == end ? DEL_BEFORE : DEL_ACROSS
|
||||
if (assoc < 0 ? pos != start : pos != end) del |= DEL_SIDE
|
||||
return new MapResult(result, del, recover)
|
||||
}
|
||||
diff += newSize - oldSize
|
||||
}
|
||||
return simple ? pos + diff : new MapResult(pos + diff, 0, null)
|
||||
}
|
||||
|
||||
/// @internal
|
||||
touches(pos: number, recover: number) {
|
||||
let diff = 0, index = recoverIndex(recover)
|
||||
let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2
|
||||
for (let i = 0; i < this.ranges.length; i += 3) {
|
||||
let start = this.ranges[i] - (this.inverted ? diff : 0)
|
||||
if (start > pos) break
|
||||
let oldSize = this.ranges[i + oldIndex], end = start + oldSize
|
||||
if (pos <= end && i == index * 3) return true
|
||||
diff += this.ranges[i + newIndex] - oldSize
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// Calls the given function on each of the changed ranges included in
|
||||
/// this map.
|
||||
forEach(f: (oldStart: number, oldEnd: number, newStart: number, newEnd: number) => void) {
|
||||
let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2
|
||||
for (let i = 0, diff = 0; i < this.ranges.length; i += 3) {
|
||||
let start = this.ranges[i], oldStart = start - (this.inverted ? diff : 0), newStart = start + (this.inverted ? 0 : diff)
|
||||
let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex]
|
||||
f(oldStart, oldStart + oldSize, newStart, newStart + newSize)
|
||||
diff += newSize - oldSize
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an inverted version of this map. The result can be used to
|
||||
/// map positions in the post-step document to the pre-step document.
|
||||
invert() {
|
||||
return new StepMap(this.ranges, !this.inverted)
|
||||
}
|
||||
|
||||
/// @internal
|
||||
toString() {
|
||||
return (this.inverted ? "-" : "") + JSON.stringify(this.ranges)
|
||||
}
|
||||
|
||||
/// Create a map that moves all positions by offset `n` (which may be
|
||||
/// negative). This can be useful when applying steps meant for a
|
||||
/// sub-document to a larger document, or vice-versa.
|
||||
static offset(n: number) {
|
||||
return n == 0 ? StepMap.empty : new StepMap(n < 0 ? [0, -n, 0] : [0, 0, n])
|
||||
}
|
||||
|
||||
/// A StepMap that contains no changed ranges.
|
||||
static empty = new StepMap([])
|
||||
}
|
||||
|
||||
/// A mapping represents a pipeline of zero or more [step
|
||||
/// maps](#transform.StepMap). It has special provisions for losslessly
|
||||
/// handling mapping positions through a series of steps in which some
|
||||
/// steps are inverted versions of earlier steps. (This comes up when
|
||||
/// ‘[rebasing](/docs/guide/#transform.rebasing)’ steps for
|
||||
/// collaboration or history management.)
|
||||
export class Mapping implements Mappable {
|
||||
/// Create a new mapping with the given position maps.
|
||||
constructor(
|
||||
/// The step maps in this mapping.
|
||||
readonly maps: StepMap[] = [],
|
||||
/// @internal
|
||||
public mirror?: number[],
|
||||
/// The starting position in the `maps` array, used when `map` or
|
||||
/// `mapResult` is called.
|
||||
public from = 0,
|
||||
/// The end position in the `maps` array.
|
||||
public to = maps.length
|
||||
) {}
|
||||
|
||||
/// Create a mapping that maps only through a part of this one.
|
||||
slice(from = 0, to = this.maps.length) {
|
||||
return new Mapping(this.maps, this.mirror, from, to)
|
||||
}
|
||||
|
||||
/// @internal
|
||||
copy() {
|
||||
return new Mapping(this.maps.slice(), this.mirror && this.mirror.slice(), this.from, this.to)
|
||||
}
|
||||
|
||||
/// Add a step map to the end of this mapping. If `mirrors` is
|
||||
/// given, it should be the index of the step map that is the mirror
|
||||
/// image of this one.
|
||||
appendMap(map: StepMap, mirrors?: number) {
|
||||
this.to = this.maps.push(map)
|
||||
if (mirrors != null) this.setMirror(this.maps.length - 1, mirrors)
|
||||
}
|
||||
|
||||
/// Add all the step maps in a given mapping to this one (preserving
|
||||
/// mirroring information).
|
||||
appendMapping(mapping: Mapping) {
|
||||
for (let i = 0, startSize = this.maps.length; i < mapping.maps.length; i++) {
|
||||
let mirr = mapping.getMirror(i)
|
||||
this.appendMap(mapping.maps[i], mirr != null && mirr < i ? startSize + mirr : undefined)
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the offset of the step map that mirrors the map at the
|
||||
/// given offset, in this mapping (as per the second argument to
|
||||
/// `appendMap`).
|
||||
getMirror(n: number): number | undefined {
|
||||
if (this.mirror) for (let i = 0; i < this.mirror.length; i++)
|
||||
if (this.mirror[i] == n) return this.mirror[i + (i % 2 ? -1 : 1)]
|
||||
}
|
||||
|
||||
/// @internal
|
||||
setMirror(n: number, m: number) {
|
||||
if (!this.mirror) this.mirror = []
|
||||
this.mirror.push(n, m)
|
||||
}
|
||||
|
||||
/// Append the inverse of the given mapping to this one.
|
||||
appendMappingInverted(mapping: Mapping) {
|
||||
for (let i = mapping.maps.length - 1, totalSize = this.maps.length + mapping.maps.length; i >= 0; i--) {
|
||||
let mirr = mapping.getMirror(i)
|
||||
this.appendMap(mapping.maps[i].invert(), mirr != null && mirr > i ? totalSize - mirr - 1 : undefined)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an inverted version of this mapping.
|
||||
invert() {
|
||||
let inverse = new Mapping
|
||||
inverse.appendMappingInverted(this)
|
||||
return inverse
|
||||
}
|
||||
|
||||
/// Map a position through this mapping.
|
||||
map(pos: number, assoc = 1) {
|
||||
if (this.mirror) return this._map(pos, assoc, true) as number
|
||||
for (let i = this.from; i < this.to; i++)
|
||||
pos = this.maps[i].map(pos, assoc)
|
||||
return pos
|
||||
}
|
||||
|
||||
/// Map a position through this mapping, returning a mapping
|
||||
/// result.
|
||||
mapResult(pos: number, assoc = 1) { return this._map(pos, assoc, false) as MapResult }
|
||||
|
||||
/// @internal
|
||||
_map(pos: number, assoc: number, simple: boolean) {
|
||||
let delInfo = 0
|
||||
|
||||
for (let i = this.from; i < this.to; i++) {
|
||||
let map = this.maps[i], result = map.mapResult(pos, assoc)
|
||||
if (result.recover != null) {
|
||||
let corr = this.getMirror(i)
|
||||
if (corr != null && corr > i && corr < this.to) {
|
||||
i = corr
|
||||
pos = this.maps[corr].recover(result.recover)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
delInfo |= result.delInfo
|
||||
pos = result.pos
|
||||
}
|
||||
|
||||
return simple ? pos : new MapResult(pos, delInfo, null)
|
||||
}
|
||||
}
|
||||
106
resources/app/node_modules/prosemirror-transform/src/mark.ts
generated
vendored
Normal file
106
resources/app/node_modules/prosemirror-transform/src/mark.ts
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
import {Mark, MarkType, Slice, Fragment, NodeType} from "prosemirror-model"
|
||||
|
||||
import {Step} from "./step"
|
||||
import {Transform} from "./transform"
|
||||
import {AddMarkStep, RemoveMarkStep} from "./mark_step"
|
||||
import {ReplaceStep} from "./replace_step"
|
||||
|
||||
export function addMark(tr: Transform, from: number, to: number, mark: Mark) {
|
||||
let removed: Step[] = [], added: Step[] = []
|
||||
let removing: RemoveMarkStep | undefined, adding: AddMarkStep | undefined
|
||||
tr.doc.nodesBetween(from, to, (node, pos, parent) => {
|
||||
if (!node.isInline) return
|
||||
let marks = node.marks
|
||||
if (!mark.isInSet(marks) && parent!.type.allowsMarkType(mark.type)) {
|
||||
let start = Math.max(pos, from), end = Math.min(pos + node.nodeSize, to)
|
||||
let newSet = mark.addToSet(marks)
|
||||
|
||||
for (let i = 0; i < marks.length; i++) {
|
||||
if (!marks[i].isInSet(newSet)) {
|
||||
if (removing && removing.to == start && removing.mark.eq(marks[i]))
|
||||
(removing as any).to = end
|
||||
else
|
||||
removed.push(removing = new RemoveMarkStep(start, end, marks[i]))
|
||||
}
|
||||
}
|
||||
|
||||
if (adding && adding.to == start)
|
||||
(adding as any).to = end
|
||||
else
|
||||
added.push(adding = new AddMarkStep(start, end, mark))
|
||||
}
|
||||
})
|
||||
|
||||
removed.forEach(s => tr.step(s))
|
||||
added.forEach(s => tr.step(s))
|
||||
}
|
||||
|
||||
export function removeMark(tr: Transform, from: number, to: number, mark?: Mark | MarkType | null) {
|
||||
let matched: {style: Mark, from: number, to: number, step: number}[] = [], step = 0
|
||||
tr.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (!node.isInline) return
|
||||
step++
|
||||
let toRemove = null
|
||||
if (mark instanceof MarkType) {
|
||||
let set = node.marks, found
|
||||
while (found = mark.isInSet(set)) {
|
||||
;(toRemove || (toRemove = [])).push(found)
|
||||
set = found.removeFromSet(set)
|
||||
}
|
||||
} else if (mark) {
|
||||
if (mark.isInSet(node.marks)) toRemove = [mark]
|
||||
} else {
|
||||
toRemove = node.marks
|
||||
}
|
||||
if (toRemove && toRemove.length) {
|
||||
let end = Math.min(pos + node.nodeSize, to)
|
||||
for (let i = 0; i < toRemove.length; i++) {
|
||||
let style = toRemove[i], found
|
||||
for (let j = 0; j < matched.length; j++) {
|
||||
let m = matched[j]
|
||||
if (m.step == step - 1 && style.eq(matched[j].style)) found = m
|
||||
}
|
||||
if (found) {
|
||||
found.to = end
|
||||
found.step = step
|
||||
} else {
|
||||
matched.push({style, from: Math.max(pos, from), to: end, step})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
matched.forEach(m => tr.step(new RemoveMarkStep(m.from, m.to, m.style)))
|
||||
}
|
||||
|
||||
export function clearIncompatible(tr: Transform, pos: number, parentType: NodeType,
|
||||
match = parentType.contentMatch,
|
||||
clearNewlines = true) {
|
||||
let node = tr.doc.nodeAt(pos)!
|
||||
let replSteps: Step[] = [], cur = pos + 1
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
let child = node.child(i), end = cur + child.nodeSize
|
||||
let allowed = match.matchType(child.type)
|
||||
if (!allowed) {
|
||||
replSteps.push(new ReplaceStep(cur, end, Slice.empty))
|
||||
} else {
|
||||
match = allowed
|
||||
for (let j = 0; j < child.marks.length; j++) if (!parentType.allowsMarkType(child.marks[j].type))
|
||||
tr.step(new RemoveMarkStep(cur, end, child.marks[j]))
|
||||
|
||||
if (clearNewlines && child.isText && parentType.whitespace != "pre") {
|
||||
let m, newline = /\r?\n|\r/g, slice
|
||||
while (m = newline.exec(child.text!)) {
|
||||
if (!slice) slice = new Slice(Fragment.from(parentType.schema.text(" ", parentType.allowedMarks(child.marks))),
|
||||
0, 0)
|
||||
replSteps.push(new ReplaceStep(cur + m.index, cur + m.index + m[0].length, slice))
|
||||
}
|
||||
}
|
||||
}
|
||||
cur = end
|
||||
}
|
||||
if (!match.validEnd) {
|
||||
let fill = match.fillBefore(Fragment.empty, true)
|
||||
tr.replace(cur, cur, new Slice(fill!, 0, 0))
|
||||
}
|
||||
for (let i = replSteps.length - 1; i >= 0; i--) tr.step(replSteps[i])
|
||||
}
|
||||
224
resources/app/node_modules/prosemirror-transform/src/mark_step.ts
generated
vendored
Normal file
224
resources/app/node_modules/prosemirror-transform/src/mark_step.ts
generated
vendored
Normal file
@@ -0,0 +1,224 @@
|
||||
import {Fragment, Slice, Node, Mark, Schema} from "prosemirror-model"
|
||||
import {Step, StepResult} from "./step"
|
||||
import {Mappable} from "./map"
|
||||
|
||||
function mapFragment(fragment: Fragment, f: (child: Node, parent: Node, i: number) => Node, parent: Node): Fragment {
|
||||
let mapped = []
|
||||
for (let i = 0; i < fragment.childCount; i++) {
|
||||
let child = fragment.child(i)
|
||||
if (child.content.size) child = child.copy(mapFragment(child.content, f, child))
|
||||
if (child.isInline) child = f(child, parent, i)
|
||||
mapped.push(child)
|
||||
}
|
||||
return Fragment.fromArray(mapped)
|
||||
}
|
||||
|
||||
/// Add a mark to all inline content between two positions.
|
||||
export class AddMarkStep extends Step {
|
||||
/// Create a mark step.
|
||||
constructor(
|
||||
/// The start of the marked range.
|
||||
readonly from: number,
|
||||
/// The end of the marked range.
|
||||
readonly to: number,
|
||||
/// The mark to add.
|
||||
readonly mark: Mark
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
apply(doc: Node) {
|
||||
let oldSlice = doc.slice(this.from, this.to), $from = doc.resolve(this.from)
|
||||
let parent = $from.node($from.sharedDepth(this.to))
|
||||
let slice = new Slice(mapFragment(oldSlice.content, (node, parent) => {
|
||||
if (!node.isAtom || !parent.type.allowsMarkType(this.mark.type)) return node
|
||||
return node.mark(this.mark.addToSet(node.marks))
|
||||
}, parent), oldSlice.openStart, oldSlice.openEnd)
|
||||
return StepResult.fromReplace(doc, this.from, this.to, slice)
|
||||
}
|
||||
|
||||
invert(): Step {
|
||||
return new RemoveMarkStep(this.from, this.to, this.mark)
|
||||
}
|
||||
|
||||
map(mapping: Mappable): Step | null {
|
||||
let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
|
||||
if (from.deleted && to.deleted || from.pos >= to.pos) return null
|
||||
return new AddMarkStep(from.pos, to.pos, this.mark)
|
||||
}
|
||||
|
||||
merge(other: Step): Step | null {
|
||||
if (other instanceof AddMarkStep &&
|
||||
other.mark.eq(this.mark) &&
|
||||
this.from <= other.to && this.to >= other.from)
|
||||
return new AddMarkStep(Math.min(this.from, other.from),
|
||||
Math.max(this.to, other.to), this.mark)
|
||||
return null
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
return {stepType: "addMark", mark: this.mark.toJSON(),
|
||||
from: this.from, to: this.to}
|
||||
}
|
||||
|
||||
/// @internal
|
||||
static fromJSON(schema: Schema, json: any) {
|
||||
if (typeof json.from != "number" || typeof json.to != "number")
|
||||
throw new RangeError("Invalid input for AddMarkStep.fromJSON")
|
||||
return new AddMarkStep(json.from, json.to, schema.markFromJSON(json.mark))
|
||||
}
|
||||
}
|
||||
|
||||
Step.jsonID("addMark", AddMarkStep)
|
||||
|
||||
/// Remove a mark from all inline content between two positions.
|
||||
export class RemoveMarkStep extends Step {
|
||||
/// Create a mark-removing step.
|
||||
constructor(
|
||||
/// The start of the unmarked range.
|
||||
readonly from: number,
|
||||
/// The end of the unmarked range.
|
||||
readonly to: number,
|
||||
/// The mark to remove.
|
||||
readonly mark: Mark
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
apply(doc: Node) {
|
||||
let oldSlice = doc.slice(this.from, this.to)
|
||||
let slice = new Slice(mapFragment(oldSlice.content, node => {
|
||||
return node.mark(this.mark.removeFromSet(node.marks))
|
||||
}, doc), oldSlice.openStart, oldSlice.openEnd)
|
||||
return StepResult.fromReplace(doc, this.from, this.to, slice)
|
||||
}
|
||||
|
||||
invert(): Step {
|
||||
return new AddMarkStep(this.from, this.to, this.mark)
|
||||
}
|
||||
|
||||
map(mapping: Mappable): Step | null {
|
||||
let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
|
||||
if (from.deleted && to.deleted || from.pos >= to.pos) return null
|
||||
return new RemoveMarkStep(from.pos, to.pos, this.mark)
|
||||
}
|
||||
|
||||
merge(other: Step): Step | null {
|
||||
if (other instanceof RemoveMarkStep &&
|
||||
other.mark.eq(this.mark) &&
|
||||
this.from <= other.to && this.to >= other.from)
|
||||
return new RemoveMarkStep(Math.min(this.from, other.from),
|
||||
Math.max(this.to, other.to), this.mark)
|
||||
return null
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
return {stepType: "removeMark", mark: this.mark.toJSON(),
|
||||
from: this.from, to: this.to}
|
||||
}
|
||||
|
||||
/// @internal
|
||||
static fromJSON(schema: Schema, json: any) {
|
||||
if (typeof json.from != "number" || typeof json.to != "number")
|
||||
throw new RangeError("Invalid input for RemoveMarkStep.fromJSON")
|
||||
return new RemoveMarkStep(json.from, json.to, schema.markFromJSON(json.mark))
|
||||
}
|
||||
}
|
||||
|
||||
Step.jsonID("removeMark", RemoveMarkStep)
|
||||
|
||||
/// Add a mark to a specific node.
|
||||
export class AddNodeMarkStep extends Step {
|
||||
/// Create a node mark step.
|
||||
constructor(
|
||||
/// The position of the target node.
|
||||
readonly pos: number,
|
||||
/// The mark to add.
|
||||
readonly mark: Mark
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
apply(doc: Node) {
|
||||
let node = doc.nodeAt(this.pos)
|
||||
if (!node) return StepResult.fail("No node at mark step's position")
|
||||
let updated = node.type.create(node.attrs, null, this.mark.addToSet(node.marks))
|
||||
return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1))
|
||||
}
|
||||
|
||||
invert(doc: Node): Step {
|
||||
let node = doc.nodeAt(this.pos)
|
||||
if (node) {
|
||||
let newSet = this.mark.addToSet(node.marks)
|
||||
if (newSet.length == node.marks.length) {
|
||||
for (let i = 0; i < node.marks.length; i++)
|
||||
if (!node.marks[i].isInSet(newSet))
|
||||
return new AddNodeMarkStep(this.pos, node.marks[i])
|
||||
return new AddNodeMarkStep(this.pos, this.mark)
|
||||
}
|
||||
}
|
||||
return new RemoveNodeMarkStep(this.pos, this.mark)
|
||||
}
|
||||
|
||||
map(mapping: Mappable): Step | null {
|
||||
let pos = mapping.mapResult(this.pos, 1)
|
||||
return pos.deletedAfter ? null : new AddNodeMarkStep(pos.pos, this.mark)
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
return {stepType: "addNodeMark", pos: this.pos, mark: this.mark.toJSON()}
|
||||
}
|
||||
|
||||
/// @internal
|
||||
static fromJSON(schema: Schema, json: any) {
|
||||
if (typeof json.pos != "number")
|
||||
throw new RangeError("Invalid input for AddNodeMarkStep.fromJSON")
|
||||
return new AddNodeMarkStep(json.pos, schema.markFromJSON(json.mark))
|
||||
}
|
||||
}
|
||||
|
||||
Step.jsonID("addNodeMark", AddNodeMarkStep)
|
||||
|
||||
/// Remove a mark from a specific node.
|
||||
export class RemoveNodeMarkStep extends Step {
|
||||
/// Create a mark-removing step.
|
||||
constructor(
|
||||
/// The position of the target node.
|
||||
readonly pos: number,
|
||||
/// The mark to remove.
|
||||
readonly mark: Mark
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
apply(doc: Node) {
|
||||
let node = doc.nodeAt(this.pos)
|
||||
if (!node) return StepResult.fail("No node at mark step's position")
|
||||
let updated = node.type.create(node.attrs, null, this.mark.removeFromSet(node.marks))
|
||||
return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1))
|
||||
}
|
||||
|
||||
invert(doc: Node): Step {
|
||||
let node = doc.nodeAt(this.pos)
|
||||
if (!node || !this.mark.isInSet(node.marks)) return this
|
||||
return new AddNodeMarkStep(this.pos, this.mark)
|
||||
}
|
||||
|
||||
map(mapping: Mappable): Step | null {
|
||||
let pos = mapping.mapResult(this.pos, 1)
|
||||
return pos.deletedAfter ? null : new RemoveNodeMarkStep(pos.pos, this.mark)
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
return {stepType: "removeNodeMark", pos: this.pos, mark: this.mark.toJSON()}
|
||||
}
|
||||
|
||||
/// @internal
|
||||
static fromJSON(schema: Schema, json: any) {
|
||||
if (typeof json.pos != "number")
|
||||
throw new RangeError("Invalid input for RemoveNodeMarkStep.fromJSON")
|
||||
return new RemoveNodeMarkStep(json.pos, schema.markFromJSON(json.mark))
|
||||
}
|
||||
}
|
||||
|
||||
Step.jsonID("removeNodeMark", RemoveNodeMarkStep)
|
||||
459
resources/app/node_modules/prosemirror-transform/src/replace.ts
generated
vendored
Normal file
459
resources/app/node_modules/prosemirror-transform/src/replace.ts
generated
vendored
Normal file
@@ -0,0 +1,459 @@
|
||||
import {Fragment, Slice, Node, ResolvedPos, NodeType, ContentMatch, Attrs} from "prosemirror-model"
|
||||
|
||||
import {Step} from "./step"
|
||||
import {ReplaceStep, ReplaceAroundStep} from "./replace_step"
|
||||
import {Transform} from "./transform"
|
||||
import {insertPoint} from "./structure"
|
||||
|
||||
/// ‘Fit’ a slice into a given position in the document, producing a
|
||||
/// [step](#transform.Step) that inserts it. Will return null if
|
||||
/// there's no meaningful way to insert the slice here, or inserting it
|
||||
/// would be a no-op (an empty slice over an empty range).
|
||||
export function replaceStep(doc: Node, from: number, to = from, slice = Slice.empty): Step | null {
|
||||
if (from == to && !slice.size) return null
|
||||
|
||||
let $from = doc.resolve(from), $to = doc.resolve(to)
|
||||
// Optimization -- avoid work if it's obvious that it's not needed.
|
||||
if (fitsTrivially($from, $to, slice)) return new ReplaceStep(from, to, slice)
|
||||
return new Fitter($from, $to, slice).fit()
|
||||
}
|
||||
|
||||
function fitsTrivially($from: ResolvedPos, $to: ResolvedPos, slice: Slice) {
|
||||
return !slice.openStart && !slice.openEnd && $from.start() == $to.start() &&
|
||||
$from.parent.canReplace($from.index(), $to.index(), slice.content)
|
||||
}
|
||||
|
||||
interface Fittable {
|
||||
sliceDepth: number
|
||||
frontierDepth: number
|
||||
parent: Node | null
|
||||
inject?: Fragment | null
|
||||
wrap?: readonly NodeType[]
|
||||
}
|
||||
|
||||
// Algorithm for 'placing' the elements of a slice into a gap:
|
||||
//
|
||||
// We consider the content of each node that is open to the left to be
|
||||
// independently placeable. I.e. in <p("foo"), p("bar")>, when the
|
||||
// paragraph on the left is open, "foo" can be placed (somewhere on
|
||||
// the left side of the replacement gap) independently from p("bar").
|
||||
//
|
||||
// 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 {
|
||||
frontier: {type: NodeType, match: ContentMatch}[] = []
|
||||
placed: Fragment = Fragment.empty
|
||||
|
||||
constructor(
|
||||
readonly $from: ResolvedPos,
|
||||
readonly $to: ResolvedPos,
|
||||
public unplaced: Slice
|
||||
) {
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
for (let i = $from.depth; i > 0; i--)
|
||||
this.placed = Fragment.from($from.node(i).copy(this.placed))
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
// 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 = this.close(moveInline < 0 ? this.$to : $from.doc.resolve(moveInline))
|
||||
if (!$to) return null
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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(): Fittable | undefined {
|
||||
let startDepth = this.unplaced.openStart
|
||||
for (let cur = this.unplaced.content, d = 0, openEnd = this.unplaced.openEnd; d < startDepth; d++) {
|
||||
let node = cur.firstChild!
|
||||
if (cur.childCount > 1) openEnd = 0
|
||||
if (node.type.spec.isolating && openEnd <= d) {
|
||||
startDepth = d
|
||||
break
|
||||
}
|
||||
cur = node.content
|
||||
}
|
||||
|
||||
// Only try wrapping nodes (pass 2) after finding a place without
|
||||
// wrapping failed.
|
||||
for (let pass = 1; pass <= 2; pass++) {
|
||||
for (let sliceDepth = pass == 1 ? startDepth : this.unplaced.openStart; sliceDepth >= 0; sliceDepth--) {
|
||||
let fragment, parent = null
|
||||
if (sliceDepth) {
|
||||
parent = contentAt(this.unplaced.content, sliceDepth - 1).firstChild
|
||||
fragment = parent!.content
|
||||
} else {
|
||||
fragment = this.unplaced.content
|
||||
}
|
||||
let first = fragment.firstChild
|
||||
for (let frontierDepth = this.depth; frontierDepth >= 0; frontierDepth--) {
|
||||
let {type, match} = this.frontier[frontierDepth], wrap, inject: Fragment | null = null
|
||||
// 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))
|
||||
: parent && 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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}: Fittable) {
|
||||
while (this.depth > frontierDepth) this.closeFrontierNode()
|
||||
if (wrap) for (let i = 0; i < wrap.length; i++) this.openFrontierNode(wrap[i])
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
let toEnd = taken == fragment.childCount
|
||||
if (!toEnd) openEndCount = -1
|
||||
|
||||
this.placed = addToFragment(this.placed, frontierDepth, Fragment.from(add))
|
||||
this.frontier[frontierDepth].match = match
|
||||
|
||||
// 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.frontier.length > 1)
|
||||
this.closeFrontierNode()
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
findCloseLevel($to: ResolvedPos) {
|
||||
scan: for (let i = Math.min(this.depth, $to.depth); i >= 0; i--) {
|
||||
let {match, type} = this.frontier[i]
|
||||
let dropInner = i < $to.depth && $to.end(i + 1) == $to.pos + ($to.depth - (i + 1))
|
||||
let fit = contentAfterFits($to, i, type, match, dropInner)
|
||||
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, move: dropInner ? $to.doc.resolve($to.after(i + 1)) : $to}
|
||||
}
|
||||
}
|
||||
|
||||
close($to: ResolvedPos) {
|
||||
let close = this.findCloseLevel($to)
|
||||
if (!close) return null
|
||||
|
||||
while (this.depth > close.depth) this.closeFrontierNode()
|
||||
if (close.fit.childCount) this.placed = addToFragment(this.placed, close.depth, close.fit)
|
||||
$to = close.move
|
||||
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, node.attrs, add)
|
||||
}
|
||||
return $to
|
||||
}
|
||||
|
||||
openFrontierNode(type: NodeType, attrs: Attrs | null = null, content?: Fragment) {
|
||||
let top = this.frontier[this.depth]
|
||||
top.match = top.match.matchType(type)!
|
||||
this.placed = addToFragment(this.placed, this.depth, Fragment.from(type.create(attrs, 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: Fragment, depth: number, count: number): Fragment {
|
||||
if (depth == 0) return fragment.cutByIndex(count, fragment.childCount)
|
||||
return fragment.replaceChild(0, fragment.firstChild!.copy(dropFromFragment(fragment.firstChild!.content, depth - 1, count)))
|
||||
}
|
||||
|
||||
function addToFragment(fragment: Fragment, depth: number, content: Fragment): Fragment {
|
||||
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: Fragment, depth: number) {
|
||||
for (let i = 0; i < depth; i++) fragment = fragment.firstChild!.content
|
||||
return fragment
|
||||
}
|
||||
|
||||
function closeNodeStart(node: Node, openStart: number, openEnd: number) {
|
||||
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)!)
|
||||
}
|
||||
return node.copy(frag)
|
||||
}
|
||||
|
||||
function contentAfterFits($to: ResolvedPos, depth: number, type: NodeType, match: ContentMatch, open: boolean) {
|
||||
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 invalidMarks(type: NodeType, fragment: Fragment, start: number) {
|
||||
for (let i = start; i < fragment.childCount; i++)
|
||||
if (!type.allowsMarks(fragment.child(i).marks)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
function definesContent(type: NodeType) {
|
||||
return type.spec.defining || type.spec.definingForContent
|
||||
}
|
||||
|
||||
export function replaceRange(tr: Transform, from: number, to: number, slice: Slice) {
|
||||
if (!slice.size) return tr.deleteRange(from, to)
|
||||
|
||||
let $from = tr.doc.resolve(from), $to = tr.doc.resolve(to)
|
||||
if (fitsTrivially($from, $to, slice))
|
||||
return tr.step(new ReplaceStep(from, to, slice))
|
||||
|
||||
let targetDepths = coveredDepths($from, tr.doc.resolve(to))
|
||||
// Can't replace the whole document, so remove 0 if it's present
|
||||
if (targetDepths[targetDepths.length - 1] == 0) targetDepths.pop()
|
||||
// Negative numbers represent not expansion over the whole node at
|
||||
// that depth, but replacing from $from.before(-D) to $to.pos.
|
||||
let preferredTarget = -($from.depth + 1)
|
||||
targetDepths.unshift(preferredTarget)
|
||||
// This loop picks a preferred target depth, if one of the covering
|
||||
// depths is not outside of a defining node, and adds negative
|
||||
// depths for any depth that has $from at its start and does not
|
||||
// cross a defining node.
|
||||
for (let d = $from.depth, pos = $from.pos - 1; d > 0; d--, pos--) {
|
||||
let spec = $from.node(d).type.spec
|
||||
if (spec.defining || spec.definingAsContext || spec.isolating) break
|
||||
if (targetDepths.indexOf(d) > -1) preferredTarget = d
|
||||
else if ($from.before(d) == pos) targetDepths.splice(1, 0, -d)
|
||||
}
|
||||
// Try to fit each possible depth of the slice into each possible
|
||||
// target depth, starting with the preferred depths.
|
||||
let preferredTargetIndex = targetDepths.indexOf(preferredTarget)
|
||||
|
||||
let leftNodes: Node[] = [], preferredDepth = slice.openStart
|
||||
for (let content = slice.content, i = 0;; i++) {
|
||||
let node = content.firstChild!
|
||||
leftNodes.push(node)
|
||||
if (i == slice.openStart) break
|
||||
content = node.content
|
||||
}
|
||||
|
||||
// Back up preferredDepth to cover defining textblocks directly
|
||||
// above it, possibly skipping a non-defining textblock.
|
||||
for (let d = preferredDepth - 1; d >= 0; d--) {
|
||||
let leftNode = leftNodes[d], def = definesContent(leftNode.type)
|
||||
if (def && !leftNode.sameMarkup($from.node(Math.abs(preferredTarget) - 1))) preferredDepth = d
|
||||
else if (def || !leftNode.type.isTextblock) break
|
||||
}
|
||||
|
||||
for (let j = slice.openStart; j >= 0; j--) {
|
||||
let openDepth = (j + preferredDepth + 1) % (slice.openStart + 1)
|
||||
let insert = leftNodes[openDepth]
|
||||
if (!insert) continue
|
||||
for (let i = 0; i < targetDepths.length; i++) {
|
||||
// Loop over possible expansion levels, starting with the
|
||||
// preferred one
|
||||
let targetDepth = targetDepths[(i + preferredTargetIndex) % targetDepths.length], expand = true
|
||||
if (targetDepth < 0) { expand = false; targetDepth = -targetDepth }
|
||||
let parent = $from.node(targetDepth - 1), index = $from.index(targetDepth - 1)
|
||||
if (parent.canReplaceWith(index, index, insert.type, insert.marks))
|
||||
return tr.replace($from.before(targetDepth), expand ? $to.after(targetDepth) : to,
|
||||
new Slice(closeFragment(slice.content, 0, slice.openStart, openDepth),
|
||||
openDepth, slice.openEnd))
|
||||
}
|
||||
}
|
||||
|
||||
let startSteps = tr.steps.length
|
||||
for (let i = targetDepths.length - 1; i >= 0; i--) {
|
||||
tr.replace(from, to, slice)
|
||||
if (tr.steps.length > startSteps) break
|
||||
let depth = targetDepths[i]
|
||||
if (depth < 0) continue
|
||||
from = $from.before(depth); to = $to.after(depth)
|
||||
}
|
||||
}
|
||||
|
||||
function closeFragment(fragment: Fragment, depth: number, oldOpen: number, newOpen: number, parent?: Node) {
|
||||
if (depth < oldOpen) {
|
||||
let first = fragment.firstChild!
|
||||
fragment = fragment.replaceChild(0, first.copy(closeFragment(first.content, depth + 1, oldOpen, newOpen, first)))
|
||||
}
|
||||
if (depth > newOpen) {
|
||||
let match = parent!.contentMatchAt(0)!
|
||||
let start = match.fillBefore(fragment)!.append(fragment)
|
||||
fragment = start.append(match.matchFragment(start)!.fillBefore(Fragment.empty, true)!)
|
||||
}
|
||||
return fragment
|
||||
}
|
||||
|
||||
export function replaceRangeWith(tr: Transform, from: number, to: number, node: Node) {
|
||||
if (!node.isInline && from == to && tr.doc.resolve(from).parent.content.size) {
|
||||
let point = insertPoint(tr.doc, from, node.type)
|
||||
if (point != null) from = to = point
|
||||
}
|
||||
tr.replaceRange(from, to, new Slice(Fragment.from(node), 0, 0))
|
||||
}
|
||||
|
||||
export function deleteRange(tr: Transform, from: number, to: number) {
|
||||
let $from = tr.doc.resolve(from), $to = tr.doc.resolve(to)
|
||||
let covered = coveredDepths($from, $to)
|
||||
for (let i = 0; i < covered.length; i++) {
|
||||
let depth = covered[i], last = i == covered.length - 1
|
||||
if ((last && depth == 0) || $from.node(depth).type.contentMatch.validEnd)
|
||||
return tr.delete($from.start(depth), $to.end(depth))
|
||||
if (depth > 0 && (last || $from.node(depth - 1).canReplace($from.index(depth - 1), $to.indexAfter(depth - 1))))
|
||||
return tr.delete($from.before(depth), $to.after(depth))
|
||||
}
|
||||
for (let d = 1; d <= $from.depth && d <= $to.depth; d++) {
|
||||
if (from - $from.start(d) == $from.depth - d && to > $from.end(d) && $to.end(d) - to != $to.depth - d)
|
||||
return tr.delete($from.before(d), to)
|
||||
}
|
||||
tr.delete(from, to)
|
||||
}
|
||||
|
||||
// Returns an array of all depths for which $from - $to spans the
|
||||
// whole content of the nodes at that depth.
|
||||
function coveredDepths($from: ResolvedPos, $to: ResolvedPos) {
|
||||
let result: number[] = [], minDepth = Math.min($from.depth, $to.depth)
|
||||
for (let d = minDepth; d >= 0; d--) {
|
||||
let start = $from.start(d)
|
||||
if (start < $from.pos - ($from.depth - d) ||
|
||||
$to.end(d) > $to.pos + ($to.depth - d) ||
|
||||
$from.node(d).type.spec.isolating ||
|
||||
$to.node(d).type.spec.isolating) break
|
||||
if (start == $to.start(d) ||
|
||||
(d == $from.depth && d == $to.depth && $from.parent.inlineContent && $to.parent.inlineContent &&
|
||||
d && $to.start(d - 1) == start - 1))
|
||||
result.push(d)
|
||||
}
|
||||
return result
|
||||
}
|
||||
178
resources/app/node_modules/prosemirror-transform/src/replace_step.ts
generated
vendored
Normal file
178
resources/app/node_modules/prosemirror-transform/src/replace_step.ts
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
import {Slice, Node, Schema} from "prosemirror-model"
|
||||
|
||||
import {Step, StepResult} from "./step"
|
||||
import {StepMap, Mappable} from "./map"
|
||||
|
||||
/// Replace a part of the document with a slice of new content.
|
||||
export class ReplaceStep extends Step {
|
||||
/// The given `slice` should fit the 'gap' between `from` and
|
||||
/// `to`—the depths must line up, and the surrounding nodes must be
|
||||
/// able to be joined with the open sides of the slice. When
|
||||
/// `structure` is true, the step will fail if the content between
|
||||
/// from and to is not just a sequence of closing and then opening
|
||||
/// tokens (this is to guard against rebased replace steps
|
||||
/// overwriting something they weren't supposed to).
|
||||
constructor(
|
||||
/// The start position of the replaced range.
|
||||
readonly from: number,
|
||||
/// The end position of the replaced range.
|
||||
readonly to: number,
|
||||
/// The slice to insert.
|
||||
readonly slice: Slice,
|
||||
/// @internal
|
||||
readonly structure = false
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
apply(doc: Node) {
|
||||
if (this.structure && contentBetween(doc, this.from, this.to))
|
||||
return StepResult.fail("Structure replace would overwrite content")
|
||||
return StepResult.fromReplace(doc, this.from, this.to, this.slice)
|
||||
}
|
||||
|
||||
getMap() {
|
||||
return new StepMap([this.from, this.to - this.from, this.slice.size])
|
||||
}
|
||||
|
||||
invert(doc: Node) {
|
||||
return new ReplaceStep(this.from, this.from + this.slice.size, doc.slice(this.from, this.to))
|
||||
}
|
||||
|
||||
map(mapping: Mappable) {
|
||||
let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
|
||||
if (from.deletedAcross && to.deletedAcross) return null
|
||||
return new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice)
|
||||
}
|
||||
|
||||
merge(other: Step) {
|
||||
if (!(other instanceof ReplaceStep) || other.structure || this.structure) return null
|
||||
|
||||
if (this.from + this.slice.size == other.from && !this.slice.openEnd && !other.slice.openStart) {
|
||||
let slice = this.slice.size + other.slice.size == 0 ? Slice.empty
|
||||
: new Slice(this.slice.content.append(other.slice.content), this.slice.openStart, other.slice.openEnd)
|
||||
return new ReplaceStep(this.from, this.to + (other.to - other.from), slice, this.structure)
|
||||
} else if (other.to == this.from && !this.slice.openStart && !other.slice.openEnd) {
|
||||
let slice = this.slice.size + other.slice.size == 0 ? Slice.empty
|
||||
: new Slice(other.slice.content.append(this.slice.content), other.slice.openStart, this.slice.openEnd)
|
||||
return new ReplaceStep(other.from, this.to, slice, this.structure)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
let json: any = {stepType: "replace", from: this.from, to: this.to}
|
||||
if (this.slice.size) json.slice = this.slice.toJSON()
|
||||
if (this.structure) json.structure = true
|
||||
return json
|
||||
}
|
||||
|
||||
/// @internal
|
||||
static fromJSON(schema: Schema, json: any) {
|
||||
if (typeof json.from != "number" || typeof json.to != "number")
|
||||
throw new RangeError("Invalid input for ReplaceStep.fromJSON")
|
||||
return new ReplaceStep(json.from, json.to, Slice.fromJSON(schema, json.slice), !!json.structure)
|
||||
}
|
||||
}
|
||||
|
||||
Step.jsonID("replace", ReplaceStep)
|
||||
|
||||
/// Replace a part of the document with a slice of content, but
|
||||
/// preserve a range of the replaced content by moving it into the
|
||||
/// slice.
|
||||
export class ReplaceAroundStep extends Step {
|
||||
/// Create a replace-around step with the given range and gap.
|
||||
/// `insert` should be the point in the slice into which the content
|
||||
/// of the gap should be moved. `structure` has the same meaning as
|
||||
/// it has in the [`ReplaceStep`](#transform.ReplaceStep) class.
|
||||
constructor(
|
||||
/// The start position of the replaced range.
|
||||
readonly from: number,
|
||||
/// The end position of the replaced range.
|
||||
readonly to: number,
|
||||
/// The start of preserved range.
|
||||
readonly gapFrom: number,
|
||||
/// The end of preserved range.
|
||||
readonly gapTo: number,
|
||||
/// The slice to insert.
|
||||
readonly slice: Slice,
|
||||
/// The position in the slice where the preserved range should be
|
||||
/// inserted.
|
||||
readonly insert: number,
|
||||
/// @internal
|
||||
readonly structure = false
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
apply(doc: Node) {
|
||||
if (this.structure && (contentBetween(doc, this.from, this.gapFrom) ||
|
||||
contentBetween(doc, this.gapTo, this.to)))
|
||||
return StepResult.fail("Structure gap-replace would overwrite content")
|
||||
|
||||
let gap = doc.slice(this.gapFrom, this.gapTo)
|
||||
if (gap.openStart || gap.openEnd)
|
||||
return StepResult.fail("Gap is not a flat range")
|
||||
let inserted = this.slice.insertAt(this.insert, gap.content)
|
||||
if (!inserted) return StepResult.fail("Content does not fit in gap")
|
||||
return StepResult.fromReplace(doc, this.from, this.to, inserted)
|
||||
}
|
||||
|
||||
getMap() {
|
||||
return new StepMap([this.from, this.gapFrom - this.from, this.insert,
|
||||
this.gapTo, this.to - this.gapTo, this.slice.size - this.insert])
|
||||
}
|
||||
|
||||
invert(doc: Node) {
|
||||
let gap = this.gapTo - this.gapFrom
|
||||
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)
|
||||
}
|
||||
|
||||
map(mapping: Mappable) {
|
||||
let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
|
||||
let gapFrom = this.from == this.gapFrom ? from.pos : mapping.map(this.gapFrom, -1)
|
||||
let gapTo = this.to == this.gapTo ? to.pos : mapping.map(this.gapTo, 1)
|
||||
if ((from.deletedAcross && to.deletedAcross) || gapFrom < from.pos || gapTo > to.pos) return null
|
||||
return new ReplaceAroundStep(from.pos, to.pos, gapFrom, gapTo, this.slice, this.insert, this.structure)
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
let json: any = {stepType: "replaceAround", from: this.from, to: this.to,
|
||||
gapFrom: this.gapFrom, gapTo: this.gapTo, insert: this.insert}
|
||||
if (this.slice.size) json.slice = this.slice.toJSON()
|
||||
if (this.structure) json.structure = true
|
||||
return json
|
||||
}
|
||||
|
||||
/// @internal
|
||||
static fromJSON(schema: Schema, json: any) {
|
||||
if (typeof json.from != "number" || typeof json.to != "number" ||
|
||||
typeof json.gapFrom != "number" || typeof json.gapTo != "number" || typeof json.insert != "number")
|
||||
throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON")
|
||||
return new ReplaceAroundStep(json.from, json.to, json.gapFrom, json.gapTo,
|
||||
Slice.fromJSON(schema, json.slice), json.insert, !!json.structure)
|
||||
}
|
||||
}
|
||||
|
||||
Step.jsonID("replaceAround", ReplaceAroundStep)
|
||||
|
||||
function contentBetween(doc: Node, from: number, to: number) {
|
||||
let $from = doc.resolve(from), dist = to - from, depth = $from.depth
|
||||
while (dist > 0 && depth > 0 && $from.indexAfter(depth) == $from.node(depth).childCount) {
|
||||
depth--
|
||||
dist--
|
||||
}
|
||||
if (dist > 0) {
|
||||
let next = $from.node(depth).maybeChild($from.indexAfter(depth))
|
||||
while (dist > 0) {
|
||||
if (!next || next.isLeaf) return true
|
||||
next = next.firstChild
|
||||
dist--
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
97
resources/app/node_modules/prosemirror-transform/src/step.ts
generated
vendored
Normal file
97
resources/app/node_modules/prosemirror-transform/src/step.ts
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
import {ReplaceError, Schema, Slice, Node} from "prosemirror-model"
|
||||
|
||||
import {StepMap, Mappable} from "./map"
|
||||
|
||||
const stepsByID: {[id: string]: {fromJSON(schema: Schema, json: any): Step}} = Object.create(null)
|
||||
|
||||
/// A step object represents an atomic change. It generally applies
|
||||
/// only to the document it was created for, since the positions
|
||||
/// stored in it will only make sense for that document.
|
||||
///
|
||||
/// New steps are defined by creating classes that extend `Step`,
|
||||
/// overriding the `apply`, `invert`, `map`, `getMap` and `fromJSON`
|
||||
/// methods, and registering your class with a unique
|
||||
/// JSON-serialization identifier using
|
||||
/// [`Step.jsonID`](#transform.Step^jsonID).
|
||||
export abstract class Step {
|
||||
/// Applies this step to the given document, returning a result
|
||||
/// object that either indicates failure, if the step can not be
|
||||
/// applied to this document, or indicates success by containing a
|
||||
/// transformed document.
|
||||
abstract apply(doc: Node): StepResult
|
||||
|
||||
/// Get the step map that represents the changes made by this step,
|
||||
/// and which can be used to transform between positions in the old
|
||||
/// and the new document.
|
||||
getMap(): StepMap { return StepMap.empty }
|
||||
|
||||
/// Create an inverted version of this step. Needs the document as it
|
||||
/// was before the step as argument.
|
||||
abstract invert(doc: Node): Step
|
||||
|
||||
/// Map this step through a mappable thing, returning either a
|
||||
/// version of that step with its positions adjusted, or `null` if
|
||||
/// the step was entirely deleted by the mapping.
|
||||
abstract map(mapping: Mappable): Step | null
|
||||
|
||||
/// Try to merge this step with another one, to be applied directly
|
||||
/// after it. Returns the merged step when possible, null if the
|
||||
/// steps can't be merged.
|
||||
merge(other: Step): Step | null { return null }
|
||||
|
||||
/// Create a JSON-serializeable representation of this step. When
|
||||
/// defining this for a custom subclass, make sure the result object
|
||||
/// includes the step type's [JSON id](#transform.Step^jsonID) under
|
||||
/// the `stepType` property.
|
||||
abstract toJSON(): any
|
||||
|
||||
/// Deserialize a step from its JSON representation. Will call
|
||||
/// through to the step class' own implementation of this method.
|
||||
static fromJSON(schema: Schema, json: any): Step {
|
||||
if (!json || !json.stepType) throw new RangeError("Invalid input for Step.fromJSON")
|
||||
let type = stepsByID[json.stepType]
|
||||
if (!type) throw new RangeError(`No step type ${json.stepType} defined`)
|
||||
return type.fromJSON(schema, json)
|
||||
}
|
||||
|
||||
/// To be able to serialize steps to JSON, each step needs a string
|
||||
/// ID to attach to its JSON representation. Use this method to
|
||||
/// register an ID for your step classes. Try to pick something
|
||||
/// that's unlikely to clash with steps from other modules.
|
||||
static jsonID(id: string, stepClass: {fromJSON(schema: Schema, json: any): Step}) {
|
||||
if (id in stepsByID) throw new RangeError("Duplicate use of step JSON ID " + id)
|
||||
stepsByID[id] = stepClass
|
||||
;(stepClass as any).prototype.jsonID = id
|
||||
return stepClass
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of [applying](#transform.Step.apply) a step. Contains either a
|
||||
/// new document or a failure value.
|
||||
export class StepResult {
|
||||
/// @internal
|
||||
constructor(
|
||||
/// The transformed document, if successful.
|
||||
readonly doc: Node | null,
|
||||
/// The failure message, if unsuccessful.
|
||||
readonly failed: string | null
|
||||
) {}
|
||||
|
||||
/// Create a successful step result.
|
||||
static ok(doc: Node) { return new StepResult(doc, null) }
|
||||
|
||||
/// Create a failed step result.
|
||||
static fail(message: string) { return new StepResult(null, message) }
|
||||
|
||||
/// Call [`Node.replace`](#model.Node.replace) with the given
|
||||
/// arguments. Create a successful result if it succeeds, and a
|
||||
/// failed one if it throws a `ReplaceError`.
|
||||
static fromReplace(doc: Node, from: number, to: number, slice: Slice) {
|
||||
try {
|
||||
return StepResult.ok(doc.replace(from, to, slice))
|
||||
} catch (e) {
|
||||
if (e instanceof ReplaceError) return StepResult.fail(e.message)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
308
resources/app/node_modules/prosemirror-transform/src/structure.ts
generated
vendored
Normal file
308
resources/app/node_modules/prosemirror-transform/src/structure.ts
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
import {Slice, Fragment, NodeRange, NodeType, Node, Mark, Attrs, ContentMatch} from "prosemirror-model"
|
||||
|
||||
import {Transform} from "./transform"
|
||||
import {ReplaceStep, ReplaceAroundStep} from "./replace_step"
|
||||
import {clearIncompatible} from "./mark"
|
||||
|
||||
function canCut(node: Node, start: number, end: number) {
|
||||
return (start == 0 || node.canReplace(start, node.childCount)) &&
|
||||
(end == node.childCount || node.canReplace(0, end))
|
||||
}
|
||||
|
||||
/// Try to find a target depth to which the content in the given range
|
||||
/// can be lifted. Will not go across
|
||||
/// [isolating](#model.NodeSpec.isolating) parent nodes.
|
||||
export function liftTarget(range: NodeRange): number | null {
|
||||
let parent = range.parent
|
||||
let content = parent.content.cutByIndex(range.startIndex, range.endIndex)
|
||||
for (let depth = range.depth;; --depth) {
|
||||
let node = range.$from.node(depth)
|
||||
let index = range.$from.index(depth), endIndex = range.$to.indexAfter(depth)
|
||||
if (depth < range.depth && node.canReplace(index, endIndex, content))
|
||||
return depth
|
||||
if (depth == 0 || node.type.spec.isolating || !canCut(node, index, endIndex)) break
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function lift(tr: Transform, range: NodeRange, target: number) {
|
||||
let {$from, $to, depth} = range
|
||||
|
||||
let gapStart = $from.before(depth + 1), gapEnd = $to.after(depth + 1)
|
||||
let start = gapStart, end = gapEnd
|
||||
|
||||
let before = Fragment.empty, openStart = 0
|
||||
for (let d = depth, splitting = false; d > target; d--)
|
||||
if (splitting || $from.index(d) > 0) {
|
||||
splitting = true
|
||||
before = Fragment.from($from.node(d).copy(before))
|
||||
openStart++
|
||||
} else {
|
||||
start--
|
||||
}
|
||||
let after = Fragment.empty, openEnd = 0
|
||||
for (let d = depth, splitting = false; d > target; d--)
|
||||
if (splitting || $to.after(d + 1) < $to.end(d)) {
|
||||
splitting = true
|
||||
after = Fragment.from($to.node(d).copy(after))
|
||||
openEnd++
|
||||
} else {
|
||||
end++
|
||||
}
|
||||
|
||||
tr.step(new ReplaceAroundStep(start, end, gapStart, gapEnd,
|
||||
new Slice(before.append(after), openStart, openEnd),
|
||||
before.size - openStart, true))
|
||||
}
|
||||
|
||||
/// Try to find a valid way to wrap the content in the given range in a
|
||||
/// node of the given type. May introduce extra nodes around and inside
|
||||
/// the wrapper node, if necessary. Returns null if no valid wrapping
|
||||
/// could be found. When `innerRange` is given, that range's content is
|
||||
/// used as the content to fit into the wrapping, instead of the
|
||||
/// content of `range`.
|
||||
export function findWrapping(
|
||||
range: NodeRange,
|
||||
nodeType: NodeType,
|
||||
attrs: Attrs | null = null,
|
||||
innerRange = range
|
||||
): {type: NodeType, attrs: Attrs | null}[] | null {
|
||||
let around = findWrappingOutside(range, nodeType)
|
||||
let inner = around && findWrappingInside(innerRange, nodeType)
|
||||
if (!inner) return null
|
||||
return (around!.map(withAttrs) as {type: NodeType, attrs: Attrs | null}[])
|
||||
.concat({type: nodeType, attrs}).concat(inner.map(withAttrs))
|
||||
}
|
||||
|
||||
function withAttrs(type: NodeType) { return {type, attrs: null} }
|
||||
|
||||
function findWrappingOutside(range: NodeRange, type: NodeType) {
|
||||
let {parent, startIndex, endIndex} = range
|
||||
let around = parent.contentMatchAt(startIndex).findWrapping(type)
|
||||
if (!around) return null
|
||||
let outer = around.length ? around[0] : type
|
||||
return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null
|
||||
}
|
||||
|
||||
function findWrappingInside(range: NodeRange, type: NodeType) {
|
||||
let {parent, startIndex, endIndex} = range
|
||||
let inner = parent.child(startIndex)
|
||||
let inside = type.contentMatch.findWrapping(inner.type)
|
||||
if (!inside) return null
|
||||
let lastType = inside.length ? inside[inside.length - 1] : type
|
||||
let innerMatch: ContentMatch | null = lastType.contentMatch
|
||||
for (let i = startIndex; innerMatch && i < endIndex; i++)
|
||||
innerMatch = innerMatch.matchType(parent.child(i).type)
|
||||
if (!innerMatch || !innerMatch.validEnd) return null
|
||||
return inside
|
||||
}
|
||||
|
||||
export function wrap(tr: Transform, range: NodeRange, wrappers: readonly {type: NodeType, attrs?: Attrs | null}[]) {
|
||||
let content = Fragment.empty
|
||||
for (let i = wrappers.length - 1; i >= 0; i--) {
|
||||
if (content.size) {
|
||||
let match = wrappers[i].type.contentMatch.matchFragment(content)
|
||||
if (!match || !match.validEnd)
|
||||
throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper")
|
||||
}
|
||||
content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content))
|
||||
}
|
||||
|
||||
let start = range.start, end = range.end
|
||||
tr.step(new ReplaceAroundStep(start, end, start, end, new Slice(content, 0, 0), wrappers.length, true))
|
||||
}
|
||||
|
||||
export function setBlockType(tr: Transform, from: number, to: number, type: NodeType, attrs: Attrs | null) {
|
||||
if (!type.isTextblock) throw new RangeError("Type given to setBlockType should be a textblock")
|
||||
let mapFrom = tr.steps.length
|
||||
tr.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (node.isTextblock && !node.hasMarkup(type, attrs) && canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
|
||||
let convertNewlines = null
|
||||
if (type.schema.linebreakReplacement) {
|
||||
let pre = type.whitespace == "pre", supportLinebreak = !!type.contentMatch.matchType(type.schema.linebreakReplacement)
|
||||
if (pre && !supportLinebreak) convertNewlines = false
|
||||
else if (!pre && supportLinebreak) convertNewlines = true
|
||||
}
|
||||
// Ensure all markup that isn't allowed in the new node type is cleared
|
||||
if (convertNewlines === false) replaceLinebreaks(tr, node, pos, mapFrom)
|
||||
clearIncompatible(tr, tr.mapping.slice(mapFrom).map(pos, 1), type, undefined, convertNewlines === null)
|
||||
let mapping = tr.mapping.slice(mapFrom)
|
||||
let startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1)
|
||||
tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1,
|
||||
new Slice(Fragment.from(type.create(attrs, null, node.marks)), 0, 0), 1, true))
|
||||
if (convertNewlines === true) replaceNewlines(tr, node, pos, mapFrom)
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function replaceNewlines(tr: Transform, node: Node, pos: number, mapFrom: number) {
|
||||
node.forEach((child, offset) => {
|
||||
if (child.isText) {
|
||||
let m, newline = /\r?\n|\r/g
|
||||
while (m = newline.exec(child.text!)) {
|
||||
let start = tr.mapping.slice(mapFrom).map(pos + 1 + offset + m.index)
|
||||
tr.replaceWith(start, start + 1, node.type.schema.linebreakReplacement!.create())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function replaceLinebreaks(tr: Transform, node: Node, pos: number, mapFrom: number) {
|
||||
node.forEach((child, offset) => {
|
||||
if (child.type == child.type.schema.linebreakReplacement) {
|
||||
let start = tr.mapping.slice(mapFrom).map(pos + 1 + offset)
|
||||
tr.replaceWith(start, start + 1, node.type.schema.text("\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function canChangeType(doc: Node, pos: number, type: NodeType) {
|
||||
let $pos = doc.resolve(pos), index = $pos.index()
|
||||
return $pos.parent.canReplaceWith(index, index + 1, type)
|
||||
}
|
||||
|
||||
/// Change the type, attributes, and/or marks of the node at `pos`.
|
||||
/// When `type` isn't given, the existing node type is preserved,
|
||||
export function setNodeMarkup(tr: Transform, pos: number, type: NodeType | undefined | null,
|
||||
attrs: Attrs | null, marks: readonly Mark[] | undefined) {
|
||||
let node = tr.doc.nodeAt(pos)
|
||||
if (!node) throw new RangeError("No node at given position")
|
||||
if (!type) type = node.type
|
||||
let newNode = type.create(attrs, null, marks || node.marks)
|
||||
if (node.isLeaf)
|
||||
return tr.replaceWith(pos, pos + node.nodeSize, newNode)
|
||||
|
||||
if (!type.validContent(node.content))
|
||||
throw new RangeError("Invalid content for node type " + type.name)
|
||||
|
||||
tr.step(new ReplaceAroundStep(pos, pos + node.nodeSize, pos + 1, pos + node.nodeSize - 1,
|
||||
new Slice(Fragment.from(newNode), 0, 0), 1, true))
|
||||
}
|
||||
|
||||
/// Check whether splitting at the given position is allowed.
|
||||
export function canSplit(doc: Node, pos: number, depth = 1,
|
||||
typesAfter?: (null | {type: NodeType, attrs?: Attrs | null})[]): boolean {
|
||||
let $pos = doc.resolve(pos), base = $pos.depth - depth
|
||||
let innerType = (typesAfter && typesAfter[typesAfter.length - 1]) || $pos.parent
|
||||
if (base < 0 || $pos.parent.type.spec.isolating ||
|
||||
!$pos.parent.canReplace($pos.index(), $pos.parent.childCount) ||
|
||||
!innerType.type.validContent($pos.parent.content.cutByIndex($pos.index(), $pos.parent.childCount)))
|
||||
return false
|
||||
for (let d = $pos.depth - 1, i = depth - 2; d > base; d--, i--) {
|
||||
let node = $pos.node(d), index = $pos.index(d)
|
||||
if (node.type.spec.isolating) return false
|
||||
let rest = node.content.cutByIndex(index, node.childCount)
|
||||
let overrideChild = typesAfter && typesAfter[i + 1]
|
||||
if (overrideChild)
|
||||
rest = rest.replaceChild(0, overrideChild.type.create(overrideChild.attrs))
|
||||
let after = (typesAfter && typesAfter[i]) || node
|
||||
if (!node.canReplace(index + 1, node.childCount) || !after.type.validContent(rest))
|
||||
return false
|
||||
}
|
||||
let index = $pos.indexAfter(base)
|
||||
let baseType = typesAfter && typesAfter[0]
|
||||
return $pos.node(base).canReplaceWith(index, index, baseType ? baseType.type : $pos.node(base + 1).type)
|
||||
}
|
||||
|
||||
export function split(tr: Transform, pos: number, depth = 1, typesAfter?: (null | {type: NodeType, attrs?: Attrs | null})[]) {
|
||||
let $pos = tr.doc.resolve(pos), before = Fragment.empty, after = Fragment.empty
|
||||
for (let d = $pos.depth, e = $pos.depth - depth, i = depth - 1; d > e; d--, i--) {
|
||||
before = Fragment.from($pos.node(d).copy(before))
|
||||
let typeAfter = typesAfter && typesAfter[i]
|
||||
after = Fragment.from(typeAfter ? typeAfter.type.create(typeAfter.attrs, after) : $pos.node(d).copy(after))
|
||||
}
|
||||
tr.step(new ReplaceStep(pos, pos, new Slice(before.append(after), depth, depth), true))
|
||||
}
|
||||
|
||||
/// Test whether the blocks before and after a given position can be
|
||||
/// joined.
|
||||
export function canJoin(doc: Node, pos: number): boolean {
|
||||
let $pos = doc.resolve(pos), index = $pos.index()
|
||||
return joinable($pos.nodeBefore, $pos.nodeAfter) &&
|
||||
$pos.parent.canReplace(index, index + 1)
|
||||
}
|
||||
|
||||
function joinable(a: Node | null, b: Node | null) {
|
||||
return !!(a && b && !a.isLeaf && a.canAppend(b))
|
||||
}
|
||||
|
||||
/// Find an ancestor of the given position that can be joined to the
|
||||
/// block before (or after if `dir` is positive). Returns the joinable
|
||||
/// point, if any.
|
||||
export function joinPoint(doc: Node, pos: number, dir = -1) {
|
||||
let $pos = doc.resolve(pos)
|
||||
for (let d = $pos.depth;; d--) {
|
||||
let before, after, index = $pos.index(d)
|
||||
if (d == $pos.depth) {
|
||||
before = $pos.nodeBefore
|
||||
after = $pos.nodeAfter
|
||||
} else if (dir > 0) {
|
||||
before = $pos.node(d + 1)
|
||||
index++
|
||||
after = $pos.node(d).maybeChild(index)
|
||||
} else {
|
||||
before = $pos.node(d).maybeChild(index - 1)
|
||||
after = $pos.node(d + 1)
|
||||
}
|
||||
if (before && !before.isTextblock && joinable(before, after) &&
|
||||
$pos.node(d).canReplace(index, index + 1)) return pos
|
||||
if (d == 0) break
|
||||
pos = dir < 0 ? $pos.before(d) : $pos.after(d)
|
||||
}
|
||||
}
|
||||
|
||||
export function join(tr: Transform, pos: number, depth: number) {
|
||||
let step = new ReplaceStep(pos - depth, pos + depth, Slice.empty, true)
|
||||
tr.step(step)
|
||||
}
|
||||
|
||||
/// Try to find a point where a node of the given type can be inserted
|
||||
/// near `pos`, by searching up the node hierarchy when `pos` itself
|
||||
/// isn't a valid place but is at the start or end of a node. Return
|
||||
/// null if no position was found.
|
||||
export function insertPoint(doc: Node, pos: number, nodeType: NodeType): number | null {
|
||||
let $pos = doc.resolve(pos)
|
||||
if ($pos.parent.canReplaceWith($pos.index(), $pos.index(), nodeType)) return pos
|
||||
|
||||
if ($pos.parentOffset == 0)
|
||||
for (let d = $pos.depth - 1; d >= 0; d--) {
|
||||
let index = $pos.index(d)
|
||||
if ($pos.node(d).canReplaceWith(index, index, nodeType)) return $pos.before(d + 1)
|
||||
if (index > 0) return null
|
||||
}
|
||||
if ($pos.parentOffset == $pos.parent.content.size)
|
||||
for (let d = $pos.depth - 1; d >= 0; d--) {
|
||||
let index = $pos.indexAfter(d)
|
||||
if ($pos.node(d).canReplaceWith(index, index, nodeType)) return $pos.after(d + 1)
|
||||
if (index < $pos.node(d).childCount) return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/// Finds a position at or around the given position where the given
|
||||
/// slice can be inserted. Will look at parent nodes' nearest boundary
|
||||
/// and try there, even if the original position wasn't directly at the
|
||||
/// start or end of that node. Returns null when no position was found.
|
||||
export function dropPoint(doc: Node, pos: number, slice: Slice): number | null {
|
||||
let $pos = doc.resolve(pos)
|
||||
if (!slice.content.size) return pos
|
||||
let content = slice.content
|
||||
for (let i = 0; i < slice.openStart; i++) content = content.firstChild!.content
|
||||
for (let pass = 1; pass <= (slice.openStart == 0 && slice.size ? 2 : 1); pass++) {
|
||||
for (let d = $pos.depth; d >= 0; d--) {
|
||||
let bias = d == $pos.depth ? 0 : $pos.pos <= ($pos.start(d + 1) + $pos.end(d + 1)) / 2 ? -1 : 1
|
||||
let insertPos = $pos.index(d) + (bias > 0 ? 1 : 0)
|
||||
let parent = $pos.node(d), fits: boolean | null = false
|
||||
if (pass == 1) {
|
||||
fits = parent.canReplace(insertPos, insertPos, content)
|
||||
} else {
|
||||
let wrapping = parent.contentMatchAt(insertPos).findWrapping(content.firstChild!.type)
|
||||
fits = wrapping && parent.canReplaceWith(insertPos, insertPos, wrapping[0])
|
||||
}
|
||||
if (fits)
|
||||
return bias == 0 ? $pos.pos : bias < 0 ? $pos.before(d + 1) : $pos.after(d + 1)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
247
resources/app/node_modules/prosemirror-transform/src/transform.ts
generated
vendored
Normal file
247
resources/app/node_modules/prosemirror-transform/src/transform.ts
generated
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
import {Node, NodeType, Mark, MarkType, ContentMatch, Slice, Fragment, NodeRange, Attrs} from "prosemirror-model"
|
||||
|
||||
import {Mapping} from "./map"
|
||||
import {Step} from "./step"
|
||||
import {addMark, removeMark, clearIncompatible} from "./mark"
|
||||
import {replaceStep, replaceRange, replaceRangeWith, deleteRange} from "./replace"
|
||||
import {lift, wrap, setBlockType, setNodeMarkup, split, join} from "./structure"
|
||||
import {AttrStep, DocAttrStep} from "./attr_step"
|
||||
import {AddNodeMarkStep, RemoveNodeMarkStep} from "./mark_step"
|
||||
|
||||
/// @internal
|
||||
export let TransformError = class extends Error {}
|
||||
|
||||
TransformError = function TransformError(this: any, message: string) {
|
||||
let err = Error.call(this, message)
|
||||
;(err as any).__proto__ = TransformError.prototype
|
||||
return err
|
||||
} as any
|
||||
|
||||
TransformError.prototype = Object.create(Error.prototype)
|
||||
TransformError.prototype.constructor = TransformError
|
||||
TransformError.prototype.name = "TransformError"
|
||||
|
||||
/// Abstraction to build up and track an array of
|
||||
/// [steps](#transform.Step) representing a document transformation.
|
||||
///
|
||||
/// Most transforming methods return the `Transform` object itself, so
|
||||
/// that they can be chained.
|
||||
export class Transform {
|
||||
/// The steps in this transform.
|
||||
readonly steps: Step[] = []
|
||||
/// The documents before each of the steps.
|
||||
readonly docs: Node[] = []
|
||||
/// A mapping with the maps for each of the steps in this transform.
|
||||
readonly mapping: Mapping = new Mapping
|
||||
|
||||
/// Create a transform that starts with the given document.
|
||||
constructor(
|
||||
/// The current document (the result of applying the steps in the
|
||||
/// transform).
|
||||
public doc: Node
|
||||
) {}
|
||||
|
||||
/// The starting document.
|
||||
get before() { return this.docs.length ? this.docs[0] : this.doc }
|
||||
|
||||
/// Apply a new step in this transform, saving the result. Throws an
|
||||
/// error when the step fails.
|
||||
step(step: Step) {
|
||||
let result = this.maybeStep(step)
|
||||
if (result.failed) throw new TransformError(result.failed)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Try to apply a step in this transformation, ignoring it if it
|
||||
/// fails. Returns the step result.
|
||||
maybeStep(step: Step) {
|
||||
let result = step.apply(this.doc)
|
||||
if (!result.failed) this.addStep(step, result.doc!)
|
||||
return result
|
||||
}
|
||||
|
||||
/// True when the document has been changed (when there are any
|
||||
/// steps).
|
||||
get docChanged() {
|
||||
return this.steps.length > 0
|
||||
}
|
||||
|
||||
/// @internal
|
||||
addStep(step: Step, doc: Node) {
|
||||
this.docs.push(this.doc)
|
||||
this.steps.push(step)
|
||||
this.mapping.appendMap(step.getMap())
|
||||
this.doc = doc
|
||||
}
|
||||
|
||||
/// Replace the part of the document between `from` and `to` with the
|
||||
/// given `slice`.
|
||||
replace(from: number, to = from, slice = Slice.empty): this {
|
||||
let step = replaceStep(this.doc, from, to, slice)
|
||||
if (step) this.step(step)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Replace the given range with the given content, which may be a
|
||||
/// fragment, node, or array of nodes.
|
||||
replaceWith(from: number, to: number, content: Fragment | Node | readonly Node[]): this {
|
||||
return this.replace(from, to, new Slice(Fragment.from(content), 0, 0))
|
||||
}
|
||||
|
||||
/// Delete the content between the given positions.
|
||||
delete(from: number, to: number): this {
|
||||
return this.replace(from, to, Slice.empty)
|
||||
}
|
||||
|
||||
/// Insert the given content at the given position.
|
||||
insert(pos: number, content: Fragment | Node | readonly Node[]): this {
|
||||
return this.replaceWith(pos, pos, content)
|
||||
}
|
||||
|
||||
/// Replace a range of the document with a given slice, using
|
||||
/// `from`, `to`, and the slice's
|
||||
/// [`openStart`](#model.Slice.openStart) property as hints, rather
|
||||
/// than fixed start and end points. This method may grow the
|
||||
/// replaced area or close open nodes in the slice in order to get a
|
||||
/// fit that is more in line with WYSIWYG expectations, by dropping
|
||||
/// fully covered parent nodes of the replaced region when they are
|
||||
/// marked [non-defining as
|
||||
/// context](#model.NodeSpec.definingAsContext), or including an
|
||||
/// open parent node from the slice that _is_ marked as [defining
|
||||
/// its content](#model.NodeSpec.definingForContent).
|
||||
///
|
||||
/// This is the method, for example, to handle paste. The similar
|
||||
/// [`replace`](#transform.Transform.replace) method is a more
|
||||
/// primitive tool which will _not_ move the start and end of its given
|
||||
/// range, and is useful in situations where you need more precise
|
||||
/// control over what happens.
|
||||
replaceRange(from: number, to: number, slice: Slice): this {
|
||||
replaceRange(this, from, to, slice)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Replace the given range with a node, but use `from` and `to` as
|
||||
/// hints, rather than precise positions. When from and to are the same
|
||||
/// and are at the start or end of a parent node in which the given
|
||||
/// node doesn't fit, this method may _move_ them out towards a parent
|
||||
/// that does allow the given node to be placed. When the given range
|
||||
/// completely covers a parent node, this method may completely replace
|
||||
/// that parent node.
|
||||
replaceRangeWith(from: number, to: number, node: Node): this {
|
||||
replaceRangeWith(this, from, to, node)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Delete the given range, expanding it to cover fully covered
|
||||
/// parent nodes until a valid replace is found.
|
||||
deleteRange(from: number, to: number): this {
|
||||
deleteRange(this, from, to)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Split the content in the given range off from its parent, if there
|
||||
/// is sibling content before or after it, and move it up the tree to
|
||||
/// the depth specified by `target`. You'll probably want to use
|
||||
/// [`liftTarget`](#transform.liftTarget) to compute `target`, to make
|
||||
/// sure the lift is valid.
|
||||
lift(range: NodeRange, target: number): this {
|
||||
lift(this, range, target)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Join the blocks around the given position. If depth is 2, their
|
||||
/// last and first siblings are also joined, and so on.
|
||||
join(pos: number, depth: number = 1): this {
|
||||
join(this, pos, depth)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Wrap the given [range](#model.NodeRange) in the given set of wrappers.
|
||||
/// The wrappers are assumed to be valid in this position, and should
|
||||
/// probably be computed with [`findWrapping`](#transform.findWrapping).
|
||||
wrap(range: NodeRange, wrappers: readonly {type: NodeType, attrs?: Attrs | null}[]): this {
|
||||
wrap(this, range, wrappers)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Set the type of all textblocks (partly) between `from` and `to` to
|
||||
/// the given node type with the given attributes.
|
||||
setBlockType(from: number, to = from, type: NodeType, attrs: Attrs | null = null): this {
|
||||
setBlockType(this, from, to, type, attrs)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Change the type, attributes, and/or marks of the node at `pos`.
|
||||
/// When `type` isn't given, the existing node type is preserved,
|
||||
setNodeMarkup(pos: number, type?: NodeType | null, attrs: Attrs | null = null, marks?: readonly Mark[]): this {
|
||||
setNodeMarkup(this, pos, type, attrs, marks)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Set a single attribute on a given node to a new value.
|
||||
/// The `pos` addresses the document content. Use `setDocAttribute`
|
||||
/// to set attributes on the document itself.
|
||||
setNodeAttribute(pos: number, attr: string, value: any): this {
|
||||
this.step(new AttrStep(pos, attr, value))
|
||||
return this
|
||||
}
|
||||
|
||||
/// Set a single attribute on the document to a new value.
|
||||
setDocAttribute(attr: string, value: any): this {
|
||||
this.step(new DocAttrStep(attr, value))
|
||||
return this
|
||||
}
|
||||
|
||||
/// Add a mark to the node at position `pos`.
|
||||
addNodeMark(pos: number, mark: Mark): this {
|
||||
this.step(new AddNodeMarkStep(pos, mark))
|
||||
return this
|
||||
}
|
||||
|
||||
/// Remove a mark (or a mark of the given type) from the node at
|
||||
/// position `pos`.
|
||||
removeNodeMark(pos: number, mark: Mark | MarkType): this {
|
||||
if (!(mark instanceof Mark)) {
|
||||
let node = this.doc.nodeAt(pos)
|
||||
if (!node) throw new RangeError("No node at position " + pos)
|
||||
mark = mark.isInSet(node.marks)!
|
||||
if (!mark) return this
|
||||
}
|
||||
this.step(new RemoveNodeMarkStep(pos, mark))
|
||||
return this
|
||||
}
|
||||
|
||||
/// Split the node at the given position, and optionally, if `depth` is
|
||||
/// greater than one, any number of nodes above that. By default, the
|
||||
/// parts split off will inherit the node type of the original node.
|
||||
/// This can be changed by passing an array of types and attributes to
|
||||
/// use after the split.
|
||||
split(pos: number, depth = 1, typesAfter?: (null | {type: NodeType, attrs?: Attrs | null})[]) {
|
||||
split(this, pos, depth, typesAfter)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Add the given mark to the inline content between `from` and `to`.
|
||||
addMark(from: number, to: number, mark: Mark): this {
|
||||
addMark(this, from, to, mark)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Remove marks from inline nodes between `from` and `to`. When
|
||||
/// `mark` is a single mark, remove precisely that mark. When it is
|
||||
/// a mark type, remove all marks of that type. When it is null,
|
||||
/// remove all marks of any type.
|
||||
removeMark(from: number, to: number, mark?: Mark | MarkType | null) {
|
||||
removeMark(this, from, to, mark)
|
||||
return this
|
||||
}
|
||||
|
||||
/// Removes all marks and nodes from the content of the node at
|
||||
/// `pos` that don't match the given new parent node type. Accepts
|
||||
/// an optional starting [content match](#model.ContentMatch) as
|
||||
/// third argument.
|
||||
clearIncompatible(pos: number, parentType: NodeType, match?: ContentMatch) {
|
||||
clearIncompatible(this, pos, parentType, match)
|
||||
return this
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user