Socket
Socket
Sign inDemoInstall

prosemirror-state

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

prosemirror-state - npm Package Compare versions

Comparing version 0.19.1 to 0.20.0

4

dist/index.js
;var assign;
((assign = require("./selection"), exports.Selection = assign.Selection, exports.TextSelection = assign.TextSelection, exports.NodeSelection = assign.NodeSelection))
((assign = require("./selection"), exports.Selection = assign.Selection, exports.SelectionRange = assign.SelectionRange, exports.TextSelection = assign.TextSelection, exports.NodeSelection = assign.NodeSelection, exports.AllSelection = assign.AllSelection, assign))

@@ -9,2 +9,2 @@ exports.Transaction = require("./transaction").Transaction

;var assign$1;
((assign$1 = require("./plugin"), exports.Plugin = assign$1.Plugin, exports.PluginKey = assign$1.PluginKey))
((assign$1 = require("./plugin"), exports.Plugin = assign$1.Plugin, exports.PluginKey = assign$1.PluginKey, assign$1))

@@ -51,2 +51,12 @@ // PluginSpec:: Object

function bindProps(obj, self, target) {
for (var prop in obj) {
var val = obj[prop]
if (val instanceof Function) { val = val.bind(self) }
else if (prop == "handleDOMEvents") { val = bindProps(val, self, {}) }
target[prop] = val
}
return target
}
// ::- Plugins wrap extra functionality that can be added to an

@@ -56,12 +66,6 @@ // editor. They can define new [state fields](#state.StateField), and

var Plugin = function Plugin(spec) {
var this$1 = this;
// :: EditorProps
// The props exported by this plugin.
this.props = {}
if (spec.props) { for (var prop in spec.props) {
var val = spec.props[prop]
if (val instanceof Function) { val = val.bind(this$1) }
this$1.props[prop] = val
} }
if (spec.props) { bindProps(spec.props, this, this.props) }
// :: Object

@@ -68,0 +72,0 @@ // The plugin's configuration object.

@@ -1,2 +0,4 @@

var warnedAboutBetween = false
var ref = require("prosemirror-model");
var Slice = ref.Slice;
var Fragment = ref.Fragment;

@@ -6,5 +8,6 @@ var classesById = Object.create(null)

// ::- Superclass for editor selections.
var Selection = function Selection($anchor, $head) {
if ( $head === void 0 ) $head = $anchor;
var Selection = function Selection($anchor, $head, ranges) {
// :: [SelectionRange]
// The ranges covered by the selection.
this.ranges = ranges || [new SelectionRange($anchor.min($head), $anchor.max($head))]
// :: ResolvedPos

@@ -20,3 +23,3 @@ // The resolved anchor of the selection (the side that stays in

var prototypeAccessors = { anchor: {},head: {},$from: {},$to: {},from: {},to: {},empty: {} };
var prototypeAccessors = { anchor: {},head: {},from: {},to: {},$from: {},$to: {},empty: {} };

@@ -33,26 +36,29 @@ // :: number

// :: number
// The lower bound of the selection's first range.
prototypeAccessors.from.get = function () { return this.$from.pos };
// :: number
// The upper bound of the selection's first range.
prototypeAccessors.to.get = function () { return this.$to.pos };
// :: ResolvedPos
// The resolved lower bound of the selection.
// The resolved lowerbound of the selection's main range.
prototypeAccessors.$from.get = function () {
return this.$head.pos < this.$anchor.pos ? this.$head : this.$anchor
return this.ranges[0].$from
};
// :: ResolvedPos
// The resolved upper bound of the selection.
// The resolved upper bound of the selection's main range.
prototypeAccessors.$to.get = function () {
return this.$head.pos < this.$anchor.pos ? this.$anchor : this.$head
return this.ranges[0].$to
};
// :: number
// The lower bound of the selection.
prototypeAccessors.from.get = function () { return this.$from.pos };
// :: number
// The upper bound of the selection.
prototypeAccessors.to.get = function () { return this.$to.pos };
// :: bool
// True if the selection is empty (head and anchor are the same).
// Indicates whether the selection contains any content.
prototypeAccessors.empty.get = function () {
return this.head == this.anchor
var ranges = this.ranges
for (var i = 0; i < ranges.length; i++)
{ if (ranges[i].$from.pos != ranges[i].$to.pos) { return false } }
return true
};

@@ -64,5 +70,2 @@

// head, and anchor.
Selection.prototype.eq = function eq (other) {
return other instanceof this.constructor && other.anchor == this.anchor && other.head == this.head
};

@@ -73,2 +76,55 @@ // map:: (doc: Node, mapping: Mappable) → Selection

// :: Slice
// Get the content of this selection as a slice.
Selection.prototype.content = function content () {
return this.$from.node(0).slice(this.from, this.to, true)
};
// :: (Transaction, ?Slice)
// Replace the selection with a slice or, if no slice is given,
// delete the selection. Will append to the given transaction.
Selection.prototype.replace = function replace (tr, content) {
if ( content === void 0 ) content = Slice.empty;
// Put the new selection at the position after the inserted
// content. When that ended in an inline node, search backwards,
// to get the position after that node. If not, search forward.
var lastNode = content.content.lastChild, lastParent = null
for (var i = 0; i < content.openRight; i++) {
lastParent = lastNode
lastNode = lastNode.lastChild
}
var mapFrom = tr.steps.length, ranges = this.ranges
for (var i$1 = 0; i$1 < ranges.length; i$1++) {
var ref = ranges[i$1];
var $from = ref.$from;
var $to = ref.$to;
var mapping = tr.mapping.slice(mapFrom)
tr.replaceRange(mapping.map($from.pos), mapping.map($to.pos), i$1 ? Slice.empty : content)
if (i$1 == 0)
{ selectionToInsertionEnd(tr, mapFrom, (lastNode ? lastNode.isInline : lastParent && lastParent.isTextblock) ? -1 : 1) }
}
};
// :: (Transaction, Node)
// Replace the selection with the given node, appending the changes
// to the given transaction.
Selection.prototype.replaceWith = function replaceWith (tr, node) {
var mapFrom = tr.steps.length, ranges = this.ranges
for (var i = 0; i < ranges.length; i++) {
var ref = ranges[i];
var $from = ref.$from;
var $to = ref.$to;
var mapping = tr.mapping.slice(mapFrom)
var from = mapping.map($from.pos), to = mapping.map($to.pos)
if (i) {
tr.deleteRange(from, to)
} else {
tr.replaceRangeWith(from, to, node)
selectionToInsertionEnd(tr, mapFrom, node.isInline ? -1 : 1)
}
}
};
// toJSON:: () → Object

@@ -80,5 +136,2 @@ // Convert the selection to a JSON representation. When implementing

// implementation adds `type`, `head`, and `anchor` properties.
Selection.prototype.toJSON = function toJSON () {
return {type: this.jsonID, anchor: this.anchor, head: this.head}
};

@@ -103,11 +156,10 @@ // :: (ResolvedPos, number, ?bool) → ?Selection

// :: (ResolvedPos, ?number, ?bool) → Selection
// :: (ResolvedPos, ?number) → Selection
// Find a valid cursor or leaf node selection near the given
// position. Searches forward first by default, but if `bias` is
// negative, it will search backwards first.
Selection.near = function near ($pos, bias, textOnly) {
Selection.near = function near ($pos, bias) {
if ( bias === void 0 ) bias = 1;
if ( textOnly === void 0 ) textOnly = false;
var result = this.findFrom($pos, bias, textOnly) || this.findFrom($pos, -bias, textOnly)
var result = this.findFrom($pos, bias) || this.findFrom($pos, -bias)
if (!result) { throw new RangeError("Searching for selection in invalid document " + $pos.node(0)) }

@@ -133,23 +185,2 @@ return result

Selection.between = function between ($anchor, $head, bias) {
if (!warnedAboutBetween && typeof console != "undefined" && console.warn) {
warnedAboutBetween = true
console.warn("Selection.between is now called TextSelection.between")
}
return TextSelection.between($anchor, $head, bias)
};
// : (Object, Mapping) → Object
// Map a JSON object representing this selection through a mapping.
Selection.mapJSON = function mapJSON (json, mapping) {
var result = {}
for (var prop in json) {
var value = json[prop]
if (prop == "anchor" || prop == "head")
{ value = mapping.map(value, json.type == "node" && prop == "head" ? -1 : 1) }
result[prop] = value
}
return result
};
// :: (Node, Object) → Selection

@@ -182,2 +213,14 @@ // Deserialize a JSON representation of a selection. Must be

// :: () → SelectionBookmark
// Get a [bookmark](#state.SelectionBookmark) for this selection,
// which is a value that can be mapped without having access to a
// current document, and later resolved to a real selection for a
// given document again. (This is used mostly by the history to
// track and restore old selections.) The default implementation of
// this method just converts the selection to a text selection and
// returns the bookmark for that.
Selection.prototype.getBookmark = function getBookmark () {
return TextSelection.between(this.anchor, this.head).getBookmark()
};
Object.defineProperties( Selection.prototype, prototypeAccessors );

@@ -192,9 +235,36 @@ exports.Selection = Selection

// ::- A text selection represents a classical editor
// selection, with a head (the moving side) and anchor (immobile
// side), both of which point into textblock nodes. It can be empty (a
// regular cursor position).
// SelectionBookmark:: interface
// A lightweight, document-independent representation of a selection.
// You can define a custom bookmark type for a custom selection class
// to make the history handle it well.
//
// map:: (mapping: Mapping) → SelectionBookmark
// Map the bookmark through a set of changes.
//
// resolve:: (doc: Node) → Selection
// Resolve the bookmark to a real selection again. This may need to
// do some error checking and may fall back to a default (usually
// [`TextSelection.between`](#state.TextSelection.between) if
// mapping made the bookmark invalid.
// ::- Represents a selected range in a document.
var SelectionRange = function SelectionRange($from, $to) {
// :: ResolvedPos
// The lower bound of the range.
this.$from = $from
// :: ResolvedPos
// The upper bound of the range.
this.$to = $to
};
exports.SelectionRange = SelectionRange
// ::- A text selection represents a classical editor selection, with
// a head (the moving side) and anchor (immobile side), both of which
// point into textblock nodes. It can be empty (a regular cursor
// position).
var TextSelection = (function (Selection) {
function TextSelection () {
Selection.apply(this, arguments);
function TextSelection($anchor, $head) {
if ( $head === void 0 ) $head = $anchor;
Selection.call(this, $anchor, $head)
}

@@ -208,3 +278,6 @@

prototypeAccessors$1.$cursor.get = function () { return this.empty ? this.$head : null };
// :: ?ResolvedPos
// Returns a resolved position if this is a cursor selection (an
// empty text selection), and null otherwise.
prototypeAccessors$1.$cursor.get = function () { return this.$anchor.pos == this.$head.pos ? this.$head : null };

@@ -218,2 +291,28 @@ TextSelection.prototype.map = function map (doc, mapping) {

TextSelection.prototype.replace = function replace (tr, content) {
if ( content === void 0 ) content = Slice.empty;
Selection.prototype.replace.call(this, tr, content)
if (content == Slice.empty) {
if (this.$from.parentOffset < this.$from.parent.content.size)
{ tr.ensureMarks(this.$from.marks(true)) }
}
};
TextSelection.prototype.eq = function eq (other) {
return other instanceof TextSelection && other.anchor == this.anchor && other.head == this.head
};
TextSelection.prototype.getBookmark = function getBookmark () {
return new TextBookmark(this.anchor, this.head)
};
TextSelection.prototype.toJSON = function toJSON () {
return {type: "text", anchor: this.anchor, head: this.head}
};
TextSelection.fromJSON = function fromJSON (doc, json) {
return new TextSelection(doc.resolve(json.anchor), doc.resolve(json.head))
};
// :: (Node, number, ?number) → TextSelection

@@ -228,14 +327,24 @@ // Create a text selection from non-resolved positions.

// :: (ResolvedPos, ResolvedPos, ?number) → TextSelection
// :: (ResolvedPos, ResolvedPos, ?number) → Selection
// Return a text selection that spans the given positions or, if
// they aren't text positions, find a text selection near them.
// `bias` determines whether the method searches forward (default)
// or backwards (negative number) first.
// or backwards (negative number) first. Will fall back to returning
// a node selection when the document doesn't contain a valid text
// position.
TextSelection.between = function between ($anchor, $head, bias) {
var dir = $anchor.pos > $head.pos ? -1 : 1
if (!$head.parent.inlineContent)
{ $head = Selection.near($head, bias || -dir, true).$head }
var dPos = $anchor.pos - $head.pos
if (!bias || dPos) { bias = dPos >= 0 ? 1 : -1 }
if (!$head.parent.inlineContent) {
var found = Selection.findFrom($head, bias, true) || Selection.findFrom($head, -bias, true)
if (found) { $head = found.$head }
else { return Selection.near($head, bias) }
}
if (!$anchor.parent.inlineContent) {
$anchor = Selection.near($anchor, dir, true).$anchor
if (($anchor.pos > $head.pos) != (dir < 0)) { $anchor = $head }
if (dPos == 0) {
$anchor = $head
} else {
$anchor = (Selection.findFrom($anchor, -bias, true) || Selection.findFrom($anchor, bias, true)).$anchor
if (($anchor.pos < $head.pos) != (dPos < 0)) { $anchor = $head }
}
}

@@ -245,10 +354,2 @@ return new TextSelection($anchor, $head)

TextSelection.fromJSON = function fromJSON (doc, json) {
// This is cautious, because the history will blindly map
// selections and then try to deserialize them, and the endpoints
// might not point at appropriate positions anymore (though they
// are guaranteed to be inside of the document's range).
return TextSelection.between(doc.resolve(json.anchor), doc.resolve(json.head))
};
Object.defineProperties( TextSelection.prototype, prototypeAccessors$1 );

@@ -262,2 +363,13 @@

var TextBookmark = function TextBookmark(anchor, head) {
this.anchor = anchor
this.head = head
};
TextBookmark.prototype.map = function map (mapping) {
return new TextBookmark(mapping.map(this.anchor), mapping.map(this.head))
};
TextBookmark.prototype.resolve = function resolve (doc) {
return TextSelection.between(doc.resolve(this.anchor), doc.resolve(this.head))
};
// ::- A node selection is a selection that points at a

@@ -268,7 +380,8 @@ // single node. All nodes marked [selectable](#model.NodeSpec.selectable)

var NodeSelection = (function (Selection) {
function NodeSelection($from) {
var $to = $from.node(0).resolve($from.pos + $from.nodeAfter.nodeSize)
Selection.call(this, $from, $to)
function NodeSelection($pos) {
var node = $pos.nodeAfter
var $end = $pos.node(0).resolve($pos.pos + node.nodeSize)
Selection.call(this, $pos, $end)
// :: Node The selected node.
this.node = $from.nodeAfter
this.node = node
}

@@ -281,9 +394,28 @@

NodeSelection.prototype.map = function map (doc, mapping) {
var from = mapping.mapResult(this.anchor, 1), to = mapping.mapResult(this.head, -1)
var $from = doc.resolve(from.pos), node = $from.nodeAfter
if (!from.deleted && !to.deleted && node && to.pos == from.pos + node.nodeSize && NodeSelection.isSelectable(node))
{ return new NodeSelection($from) }
return Selection.near($from)
var ref = mapping.mapResult(this.anchor);
var deleted = ref.deleted;
var pos = ref.pos;
var $pos = doc.resolve(pos)
if (deleted) { return Selection.near($pos) }
return new NodeSelection($pos)
};
NodeSelection.prototype.content = function content () {
return new Slice(Fragment.from(this.node), 0, 0)
};
NodeSelection.prototype.eq = function eq (other) {
return other instanceof NodeSelection && other.anchor == this.anchor
};
NodeSelection.prototype.toJSON = function toJSON () {
return {type: "node", anchor: this.anchor}
};
NodeSelection.prototype.getBookmark = function getBookmark () { return new NodeBookmark(this.anchor) };
NodeSelection.fromJSON = function fromJSON (doc, json) {
return new NodeSelection(doc.resolve(json.anchor))
};
// :: (Node, number, ?number) → TextSelection

@@ -302,8 +434,2 @@ // Create a node selection from non-resolved positions.

NodeSelection.fromJSON = function fromJSON (doc, json) {
var $from = doc.resolve(json.anchor), node = $from.nodeAfter
if (node && json.head == json.anchor + node.nodeSize && NodeSelection.isSelectable(node)) { return new NodeSelection($from) }
else { return Selection.near($from) }
};
return NodeSelection;

@@ -317,2 +443,51 @@ }(Selection));

var NodeBookmark = function NodeBookmark(anchor) {
this.anchor = anchor
};
NodeBookmark.prototype.map = function map (mapping) {
var ref = mapping.mapResult(this.anchor);
var deleted = ref.deleted;
var pos = ref.pos;
return deleted ? new TextBookmark(pos, pos) : new NodeBookmark(pos)
};
NodeBookmark.prototype.resolve = function resolve (doc) {
var $pos = doc.resolve(this.anchor), node = $pos.nodeAfter
if (node && NodeSelection.isSelectable(node)) { return new NodeSelection($pos) }
return Selection.near($pos)
};
// ::- A selection type that represents selecting the whole document
// (which can not necessarily be expressed with a text selection, when
// there are for example leaf block nodes at the start or end of the
// document).
var AllSelection = (function (Selection) {
function AllSelection(doc) {
Selection.call(this, doc.resolve(0), doc.resolve(doc.content.size))
}
if ( Selection ) AllSelection.__proto__ = Selection;
AllSelection.prototype = Object.create( Selection && Selection.prototype );
AllSelection.prototype.constructor = AllSelection;
AllSelection.prototype.toJSON = function toJSON () { return {type: "all"} };
AllSelection.fromJSON = function fromJSON (doc) { return new AllSelection(doc) };
AllSelection.prototype.map = function map (doc) { return new AllSelection(doc) };
AllSelection.prototype.eq = function eq (other) { return other instanceof AllSelection };
AllSelection.prototype.getBookmark = function getBookmark () { return AllBookmark };
return AllSelection;
}(Selection));
exports.AllSelection = AllSelection
Selection.jsonID("all", AllSelection)
var AllBookmark = {
map: function map() { return this },
resolve: function resolve(doc) { return new AllSelection(doc) }
}
// FIXME we'll need some awareness of text direction when scanning for selections

@@ -336,1 +511,8 @@

}
function selectionToInsertionEnd(tr, startLen, bias) {
if (tr.steps.length == startLen) { return }
var map = tr.mapping.maps[tr.mapping.maps.length - 1], end
map.forEach(function (_from, _to, _newFrom, newTo) { return end = newTo; })
if (end != null) { tr.setSelection(Selection.near(tr.doc.resolve(end), bias)) }
}

@@ -32,3 +32,3 @@ var ref = require("prosemirror-model");

init: function init() { return null },
apply: function apply(tr, _marks, _old, state) { return state.selection.empty ? tr.storedMarks : null }
apply: function apply(tr, _marks, _old, state) { return state.selection.$cursor ? tr.storedMarks : null }
}),

@@ -139,2 +139,3 @@

if (tr$1 && newState.filterTransaction(tr$1, i)) {
tr$1.setMeta("appendedTransaction", tr$1)
if (!seen) {

@@ -141,0 +142,0 @@ seen = []

@@ -5,4 +5,2 @@ var ref = require("prosemirror-transform");

var Mark = ref$1.Mark;
var ref$2 = require("./selection");
var Selection = ref$2.Selection;

@@ -123,16 +121,3 @@ var UPDATED_SEL = 1, UPDATED_MARKS = 2, UPDATED_SCROLL = 4

Transaction.prototype.replaceSelection = function replaceSelection (slice) {
var ref = this.selection;
var from = ref.from;
var to = ref.to;
var startLen = this.steps.length
this.replaceRange(from, to, slice)
// Move the selection to the position after the inserted content.
// When that ended in an inline node, search backwards, to get the
// position after that node. If not, search forward.
var lastNode = slice.content.lastChild, lastParent = null
for (var i = 0; i < slice.openRight; i++) {
lastParent = lastNode
lastNode = lastNode.lastChild
}
selectionToInsertionEnd(this, startLen, (lastNode ? lastNode.isInline : lastParent && lastParent.isTextblock) ? -1 : 1)
this.selection.replace(this, slice)
return this

@@ -147,11 +132,6 @@ };

Transaction.prototype.replaceSelectionWith = function replaceSelectionWith (node, inheritMarks) {
var ref = this.selection;
var $from = ref.$from;
var from = ref.from;
var to = ref.to;
var startLen = this.steps.length
var selection = this.selection
if (inheritMarks !== false)
{ node = node.mark(this.storedMarks || $from.marks(to > from)) }
this.replaceRangeWith(from, to, node)
selectionToInsertionEnd(this, startLen, node.isInline ? -1 : 1)
{ node = node.mark(this.storedMarks || selection.$from.marks(selection.to > selection.from)) }
selection.replaceWith(this, node)
return this

@@ -163,8 +143,3 @@ };

Transaction.prototype.deleteSelection = function deleteSelection () {
var ref = this.selection;
var from = ref.from;
var to = ref.to;
var $from = ref.$from;
this.deleteRange(from, to)
if ($from.parentOffset < $from.parent.content.size) { this.ensureMarks($from.marks(true)) }
this.selection.replace(this)
return this

@@ -243,8 +218,1 @@ };

exports.Transaction = Transaction
function selectionToInsertionEnd(tr, startLen, bias) {
if (tr.steps.length == startLen) { return }
var map = tr.mapping.maps[tr.mapping.maps.length - 1], end
map.forEach(function (_from, _to, _newFrom, newTo) { return end = newTo; })
if (end != null) { tr.setSelection(Selection.near(tr.doc.resolve(end), bias)) }
}
{
"name": "prosemirror-state",
"version": "0.19.1",
"version": "0.20.0",
"description": "ProseMirror editor state",

@@ -19,4 +19,4 @@ "main": "dist/index.js",

"dependencies": {
"prosemirror-model": "^0.19.0",
"prosemirror-transform": "^0.19.0"
"prosemirror-model": "^0.20.0",
"prosemirror-transform": "^0.20.0"
},

@@ -27,3 +27,4 @@ "devDependencies": {

"ist": "^1.0.0",
"rimraf": "^2.5.4"
"rimraf": "^2.5.4",
"prosemirror-test-builder": "^0.20.0"
},

@@ -30,0 +31,0 @@ "scripts": {

;({Selection: exports.Selection,
SelectionRange: exports.SelectionRange,
TextSelection: exports.TextSelection,
NodeSelection: exports.NodeSelection} = require("./selection"))
NodeSelection: exports.NodeSelection,
AllSelection: exports.AllSelection} = require("./selection"))

@@ -5,0 +7,0 @@ exports.Transaction = require("./transaction").Transaction

@@ -51,2 +51,12 @@ // PluginSpec:: Object

function bindProps(obj, self, target) {
for (let prop in obj) {
let val = obj[prop]
if (val instanceof Function) val = val.bind(self)
else if (prop == "handleDOMEvents") val = bindProps(val, self, {})
target[prop] = val
}
return target
}
// ::- Plugins wrap extra functionality that can be added to an

@@ -62,7 +72,3 @@ // editor. They can define new [state fields](#state.StateField), and

this.props = {}
if (spec.props) for (let prop in spec.props) {
let val = spec.props[prop]
if (val instanceof Function) val = val.bind(this)
this.props[prop] = val
}
if (spec.props) bindProps(spec.props, this, this.props)
// :: Object

@@ -69,0 +75,0 @@ // The plugin's configuration object.

@@ -24,3 +24,7 @@ This module implements the state object of a ProseMirror editor, along

@NodeSelection
@AllSelection
@SelectionRange
@SelectionBookmark
### Plugin System

@@ -27,0 +31,0 @@

@@ -1,2 +0,2 @@

let warnedAboutBetween = false
const {Slice, Fragment} = require("prosemirror-model")

@@ -7,3 +7,10 @@ const classesById = Object.create(null)

class Selection {
constructor($anchor, $head = $anchor) {
// :: (ResolvedPos, ResolvedPos, ?[SelectionRange])
// Initialize a selection with the head and anchor and ranges. If no
// ranges are given, constructs a single range across `$anchor` and
// `$head`.
constructor($anchor, $head, ranges) {
// :: [SelectionRange]
// The ranges covered by the selection.
this.ranges = ranges || [new SelectionRange($anchor.min($head), $anchor.max($head))]
// :: ResolvedPos

@@ -29,26 +36,29 @@ // The resolved anchor of the selection (the side that stays in

// :: number
// The lower bound of the selection's first range.
get from() { return this.$from.pos }
// :: number
// The upper bound of the selection's first range.
get to() { return this.$to.pos }
// :: ResolvedPos
// The resolved lower bound of the selection.
// The resolved lower bound of the selection's main range.
get $from() {
return this.$head.pos < this.$anchor.pos ? this.$head : this.$anchor
return this.ranges[0].$from
}
// :: ResolvedPos
// The resolved upper bound of the selection.
// The resolved upper bound of the selection's main range.
get $to() {
return this.$head.pos < this.$anchor.pos ? this.$anchor : this.$head
return this.ranges[0].$to
}
// :: number
// The lower bound of the selection.
get from() { return this.$from.pos }
// :: number
// The upper bound of the selection.
get to() { return this.$to.pos }
// :: bool
// True if the selection is empty (head and anchor are the same).
// Indicates whether the selection contains any content.
get empty() {
return this.head == this.anchor
let ranges = this.ranges
for (let i = 0; i < ranges.length; i++)
if (ranges[i].$from.pos != ranges[i].$to.pos) return false
return true
}

@@ -60,5 +70,2 @@

// head, and anchor.
eq(other) {
return other instanceof this.constructor && other.anchor == this.anchor && other.head == this.head
}

@@ -69,2 +76,47 @@ // map:: (doc: Node, mapping: Mappable) → Selection

// :: Slice
// Get the content of this selection as a slice.
content() {
return this.$from.node(0).slice(this.from, this.to, true)
}
// :: (Transaction, ?Slice)
// Replace the selection with a slice or, if no slice is given,
// delete the selection. Will append to the given transaction.
replace(tr, content = Slice.empty) {
// Put the new selection at the position after the inserted
// content. When that ended in an inline node, search backwards,
// to get the position after that node. If not, search forward.
let lastNode = content.content.lastChild, lastParent = null
for (let i = 0; i < content.openRight; i++) {
lastParent = lastNode
lastNode = lastNode.lastChild
}
let mapFrom = tr.steps.length, ranges = this.ranges
for (let i = 0; i < ranges.length; i++) {
let {$from, $to} = ranges[i], mapping = tr.mapping.slice(mapFrom)
tr.replaceRange(mapping.map($from.pos), mapping.map($to.pos), i ? Slice.empty : content)
if (i == 0)
selectionToInsertionEnd(tr, mapFrom, (lastNode ? lastNode.isInline : lastParent && lastParent.isTextblock) ? -1 : 1)
}
}
// :: (Transaction, Node)
// Replace the selection with the given node, appending the changes
// to the given transaction.
replaceWith(tr, node) {
let mapFrom = tr.steps.length, ranges = this.ranges
for (let i = 0; i < ranges.length; i++) {
let {$from, $to} = ranges[i], mapping = tr.mapping.slice(mapFrom)
let from = mapping.map($from.pos), to = mapping.map($to.pos)
if (i) {
tr.deleteRange(from, to)
} else {
tr.replaceRangeWith(from, to, node)
selectionToInsertionEnd(tr, mapFrom, node.isInline ? -1 : 1)
}
}
}
// toJSON:: () → Object

@@ -76,5 +128,2 @@ // Convert the selection to a JSON representation. When implementing

// implementation adds `type`, `head`, and `anchor` properties.
toJSON() {
return {type: this.jsonID, anchor: this.anchor, head: this.head}
}

@@ -99,8 +148,8 @@ // :: (ResolvedPos, number, ?bool) → ?Selection

// :: (ResolvedPos, ?number, ?bool) → Selection
// :: (ResolvedPos, ?number) → Selection
// Find a valid cursor or leaf node selection near the given
// position. Searches forward first by default, but if `bias` is
// negative, it will search backwards first.
static near($pos, bias = 1, textOnly = false) {
let result = this.findFrom($pos, bias, textOnly) || this.findFrom($pos, -bias, textOnly)
static near($pos, bias = 1) {
let result = this.findFrom($pos, bias) || this.findFrom($pos, -bias)
if (!result) throw new RangeError("Searching for selection in invalid document " + $pos.node(0))

@@ -126,23 +175,2 @@ return result

static between($anchor, $head, bias) {
if (!warnedAboutBetween && typeof console != "undefined" && console.warn) {
warnedAboutBetween = true
console.warn("Selection.between is now called TextSelection.between")
}
return TextSelection.between($anchor, $head, bias)
}
// : (Object, Mapping) → Object
// Map a JSON object representing this selection through a mapping.
static mapJSON(json, mapping) {
let result = {}
for (let prop in json) {
let value = json[prop]
if (prop == "anchor" || prop == "head")
value = mapping.map(value, json.type == "node" && prop == "head" ? -1 : 1)
result[prop] = value
}
return result
}
// :: (Node, Object) → Selection

@@ -174,2 +202,14 @@ // Deserialize a JSON representation of a selection. Must be

}
// :: () → SelectionBookmark
// Get a [bookmark](#state.SelectionBookmark) for this selection,
// which is a value that can be mapped without having access to a
// current document, and later resolved to a real selection for a
// given document again. (This is used mostly by the history to
// track and restore old selections.) The default implementation of
// this method just converts the selection to a text selection and
// returns the bookmark for that.
getBookmark() {
return TextSelection.between(this.anchor, this.head).getBookmark()
}
}

@@ -184,11 +224,45 @@ exports.Selection = Selection

// ::- A text selection represents a classical editor
// selection, with a head (the moving side) and anchor (immobile
// side), both of which point into textblock nodes. It can be empty (a
// regular cursor position).
// SelectionBookmark:: interface
// A lightweight, document-independent representation of a selection.
// You can define a custom bookmark type for a custom selection class
// to make the history handle it well.
//
// map:: (mapping: Mapping) → SelectionBookmark
// Map the bookmark through a set of changes.
//
// resolve:: (doc: Node) → Selection
// Resolve the bookmark to a real selection again. This may need to
// do some error checking and may fall back to a default (usually
// [`TextSelection.between`](#state.TextSelection.between) if
// mapping made the bookmark invalid.
// ::- Represents a selected range in a document.
class SelectionRange {
// :: (ResolvedPos, ResolvedPos)
constructor($from, $to) {
// :: ResolvedPos
// The lower bound of the range.
this.$from = $from
// :: ResolvedPos
// The upper bound of the range.
this.$to = $to
}
}
exports.SelectionRange = SelectionRange
// ::- A text selection represents a classical editor selection, with
// a head (the moving side) and anchor (immobile side), both of which
// point into textblock nodes. It can be empty (a regular cursor
// position).
class TextSelection extends Selection {
// :: (ResolvedPos, ?ResolvedPos)
// Construct a text selection between the given points.
constructor($anchor, $head = $anchor) {
super($anchor, $head)
}
// :: ?ResolvedPos
// Returns a resolved position if this is a cursor selection (an
// empty text selection), and null otherwise.
get $cursor() { return this.empty ? this.$head : null }
get $cursor() { return this.$anchor.pos == this.$head.pos ? this.$head : null }

@@ -202,2 +276,26 @@ map(doc, mapping) {

replace(tr, content = Slice.empty) {
super.replace(tr, content)
if (content == Slice.empty) {
if (this.$from.parentOffset < this.$from.parent.content.size)
tr.ensureMarks(this.$from.marks(true))
}
}
eq(other) {
return other instanceof TextSelection && other.anchor == this.anchor && other.head == this.head
}
getBookmark() {
return new TextBookmark(this.anchor, this.head)
}
toJSON() {
return {type: "text", anchor: this.anchor, head: this.head}
}
static fromJSON(doc, json) {
return new TextSelection(doc.resolve(json.anchor), doc.resolve(json.head))
}
// :: (Node, number, ?number) → TextSelection

@@ -210,25 +308,27 @@ // Create a text selection from non-resolved positions.

// :: (ResolvedPos, ResolvedPos, ?number) → TextSelection
// :: (ResolvedPos, ResolvedPos, ?number) → Selection
// Return a text selection that spans the given positions or, if
// they aren't text positions, find a text selection near them.
// `bias` determines whether the method searches forward (default)
// or backwards (negative number) first.
// or backwards (negative number) first. Will fall back to returning
// a node selection when the document doesn't contain a valid text
// position.
static between($anchor, $head, bias) {
let dir = $anchor.pos > $head.pos ? -1 : 1
if (!$head.parent.inlineContent)
$head = Selection.near($head, bias || -dir, true).$head
let dPos = $anchor.pos - $head.pos
if (!bias || dPos) bias = dPos >= 0 ? 1 : -1
if (!$head.parent.inlineContent) {
let found = Selection.findFrom($head, bias, true) || Selection.findFrom($head, -bias, true)
if (found) $head = found.$head
else return Selection.near($head, bias)
}
if (!$anchor.parent.inlineContent) {
$anchor = Selection.near($anchor, dir, true).$anchor
if (($anchor.pos > $head.pos) != (dir < 0)) $anchor = $head
if (dPos == 0) {
$anchor = $head
} else {
$anchor = (Selection.findFrom($anchor, -bias, true) || Selection.findFrom($anchor, bias, true)).$anchor
if (($anchor.pos < $head.pos) != (dPos < 0)) $anchor = $head
}
}
return new TextSelection($anchor, $head)
}
static fromJSON(doc, json) {
// This is cautious, because the history will blindly map
// selections and then try to deserialize them, and the endpoints
// might not point at appropriate positions anymore (though they
// are guaranteed to be inside of the document's range).
return TextSelection.between(doc.resolve(json.anchor), doc.resolve(json.head))
}
}

@@ -239,2 +339,15 @@ exports.TextSelection = TextSelection

class TextBookmark {
constructor(anchor, head) {
this.anchor = anchor
this.head = head
}
map(mapping) {
return new TextBookmark(mapping.map(this.anchor), mapping.map(this.head))
}
resolve(doc) {
return TextSelection.between(doc.resolve(this.anchor), doc.resolve(this.head))
}
}
// ::- A node selection is a selection that points at a

@@ -248,17 +361,35 @@ // single node. All nodes marked [selectable](#model.NodeSpec.selectable)

// argument.
constructor($from) {
let $to = $from.node(0).resolve($from.pos + $from.nodeAfter.nodeSize)
super($from, $to)
constructor($pos) {
let node = $pos.nodeAfter
let $end = $pos.node(0).resolve($pos.pos + node.nodeSize)
super($pos, $end)
// :: Node The selected node.
this.node = $from.nodeAfter
this.node = node
}
map(doc, mapping) {
let from = mapping.mapResult(this.anchor, 1), to = mapping.mapResult(this.head, -1)
let $from = doc.resolve(from.pos), node = $from.nodeAfter
if (!from.deleted && !to.deleted && node && to.pos == from.pos + node.nodeSize && NodeSelection.isSelectable(node))
return new NodeSelection($from)
return Selection.near($from)
let {deleted, pos} = mapping.mapResult(this.anchor)
let $pos = doc.resolve(pos)
if (deleted) return Selection.near($pos)
return new NodeSelection($pos)
}
content() {
return new Slice(Fragment.from(this.node), 0, 0)
}
eq(other) {
return other instanceof NodeSelection && other.anchor == this.anchor
}
toJSON() {
return {type: "node", anchor: this.anchor}
}
getBookmark() { return new NodeBookmark(this.anchor) }
static fromJSON(doc, json) {
return new NodeSelection(doc.resolve(json.anchor))
}
// :: (Node, number, ?number) → TextSelection

@@ -276,8 +407,2 @@ // Create a node selection from non-resolved positions.

}
static fromJSON(doc, json) {
let $from = doc.resolve(json.anchor), node = $from.nodeAfter
if (node && json.head == json.anchor + node.nodeSize && NodeSelection.isSelectable(node)) return new NodeSelection($from)
else return Selection.near($from)
}
}

@@ -290,2 +415,45 @@ exports.NodeSelection = NodeSelection

class NodeBookmark {
constructor(anchor) {
this.anchor = anchor
}
map(mapping) {
let {deleted, pos} = mapping.mapResult(this.anchor)
return deleted ? new TextBookmark(pos, pos) : new NodeBookmark(pos)
}
resolve(doc) {
let $pos = doc.resolve(this.anchor), node = $pos.nodeAfter
if (node && NodeSelection.isSelectable(node)) return new NodeSelection($pos)
return Selection.near($pos)
}
}
// ::- A selection type that represents selecting the whole document
// (which can not necessarily be expressed with a text selection, when
// there are for example leaf block nodes at the start or end of the
// document).
class AllSelection extends Selection {
constructor(doc) {
super(doc.resolve(0), doc.resolve(doc.content.size))
}
toJSON() { return {type: "all"} }
static fromJSON(doc) { return new AllSelection(doc) }
map(doc) { return new AllSelection(doc) }
eq(other) { return other instanceof AllSelection }
getBookmark() { return AllBookmark }
}
exports.AllSelection = AllSelection
Selection.jsonID("all", AllSelection)
const AllBookmark = {
map() { return this },
resolve(doc) { return new AllSelection(doc) }
}
// FIXME we'll need some awareness of text direction when scanning for selections

@@ -309,1 +477,8 @@

}
function selectionToInsertionEnd(tr, startLen, bias) {
if (tr.steps.length == startLen) return
let map = tr.mapping.maps[tr.mapping.maps.length - 1], end
map.forEach((_from, _to, _newFrom, newTo) => end = newTo)
if (end != null) tr.setSelection(Selection.near(tr.doc.resolve(end), bias))
}

@@ -31,3 +31,3 @@ const {Node} = require("prosemirror-model")

init() { return null },
apply(tr, _marks, _old, state) { return state.selection.empty ? tr.storedMarks : null }
apply(tr, _marks, _old, state) { return state.selection.$cursor ? tr.storedMarks : null }
}),

@@ -132,2 +132,3 @@

if (tr && newState.filterTransaction(tr, i)) {
tr.setMeta("appendedTransaction", tr)
if (!seen) {

@@ -134,0 +135,0 @@ seen = []

const {Transform} = require("prosemirror-transform")
const {Mark} = require("prosemirror-model")
const {Selection} = require("./selection")

@@ -113,13 +112,3 @@ const UPDATED_SEL = 1, UPDATED_MARKS = 2, UPDATED_SCROLL = 4

replaceSelection(slice) {
let {from, to} = this.selection, startLen = this.steps.length
this.replaceRange(from, to, slice)
// Move the selection to the position after the inserted content.
// When that ended in an inline node, search backwards, to get the
// position after that node. If not, search forward.
let lastNode = slice.content.lastChild, lastParent = null
for (let i = 0; i < slice.openRight; i++) {
lastParent = lastNode
lastNode = lastNode.lastChild
}
selectionToInsertionEnd(this, startLen, (lastNode ? lastNode.isInline : lastParent && lastParent.isTextblock) ? -1 : 1)
this.selection.replace(this, slice)
return this

@@ -134,7 +123,6 @@ }

replaceSelectionWith(node, inheritMarks) {
let {$from, from, to} = this.selection, startLen = this.steps.length
let selection = this.selection
if (inheritMarks !== false)
node = node.mark(this.storedMarks || $from.marks(to > from))
this.replaceRangeWith(from, to, node)
selectionToInsertionEnd(this, startLen, node.isInline ? -1 : 1)
node = node.mark(this.storedMarks || selection.$from.marks(selection.to > selection.from))
selection.replaceWith(this, node)
return this

@@ -146,5 +134,3 @@ }

deleteSelection() {
let {from, to, $from} = this.selection
this.deleteRange(from, to)
if ($from.parentOffset < $from.parent.content.size) this.ensureMarks($from.marks(true))
this.selection.replace(this)
return this

@@ -215,8 +201,1 @@ }

exports.Transaction = Transaction
function selectionToInsertionEnd(tr, startLen, bias) {
if (tr.steps.length == startLen) return
let map = tr.mapping.maps[tr.mapping.maps.length - 1], end
map.forEach((_from, _to, _newFrom, newTo) => end = newTo)
if (end != null) tr.setSelection(Selection.near(tr.doc.resolve(end), bias))
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc