Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

prosemirror-model

Package Overview
Dependencies
Maintainers
1
Versions
85
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

prosemirror-model - npm Package Compare versions

Comparing version 0.22.0 to 0.23.0

dist/index.js.map

15

CONTRIBUTING.md

@@ -16,3 +16,3 @@ # How to contribute

[GitHub issue tracker](http://github.com/prosemirror/prosemirror/issues).
Before reporting a bug, read these pointers.
Before reporting a bug, please read these pointers.

@@ -26,6 +26,7 @@ - The issue tracker is for *bugs*, not requests for help. Questions

- Mention very precisely what went wrong. "X is broken" is not a good bug
report. What did you expect to happen? What happened instead? Describe the
exact steps a maintainer has to take to make the problem occur. We can not
fix something that we can not observe.
- Mention very precisely what went wrong. "X is broken" is not a good
bug report. What did you expect to happen? What happened instead?
Describe the exact steps a maintainer has to take to make the
problem occur. A screencast can be useful, but is no substitute for
a textual description.

@@ -50,4 +51,4 @@ - A great way to make it easy to reproduce your problem, if it can not

- Follow the code style of the rest of the project (see below). Run
`npm run lint` (in the main repository checkout) that the linter is
happy.
`npm run lint` (in the main repository checkout) to make sure that
the linter is happy.

@@ -54,0 +55,0 @@ - If your changes are easy to test or likely to regress, add tests in

{
"name": "prosemirror-model",
"version": "0.22.0",
"version": "0.23.0",
"description": "ProseMirror's document model",

@@ -22,14 +22,15 @@ "main": "dist/index.js",

"devDependencies": {
"buble": "^0.15.1",
"mocha": "^3.0.2",
"ist": "^1.0.0",
"jsdom": "^10.1.0",
"rimraf": "^2.5.4",
"prosemirror-test-builder": "^0.22.0"
"prosemirror-test-builder": "^0.23.0",
"rollup": "^0.49.0",
"rollup-plugin-buble": "^0.15.0"
},
"scripts": {
"test": "mocha test/test-*.js",
"build": "rimraf dist && buble -i src -o dist --no-named-function-expr",
"prepublish": "npm run build"
"build": "rollup -c",
"watch": "rollup -c -w",
"prepare": "npm run build"
}
}

@@ -9,15 +9,11 @@ # prosemirror-model

This [module](http://prosemirror.net/ref.html#model) implements
ProseMirror's [document model](http://prosemirror.net/guide/doc.html),
This [module](http://prosemirror.net/docs/ref/#model) implements
ProseMirror's [document model](http://prosemirror\.net/docs/guide/#doc),
along with the mechanisms needed to support
[schemas](http://prosemirror.net/guide/schema.html).
[schemas](http://prosemirror\.net/docs/guide/#schema).
The [project page](http://prosemirror.net) has more information, a
number of [demos](http://prosemirror.net/#demos) and the
[documentation](http://prosemirror.net/docs.html).
number of [examples](http://prosemirror.net/examples/) and the
[documentation](http://prosemirror.net/docs/).
**NOTE:** This project is in *BETA* stage. It isn't thoroughly tested,
and the API might still change across `0.x` releases. You are welcome
to use it, but don't expect it to be very stable yet.
This code is released under an

@@ -24,0 +20,0 @@ [MIT license](https://github.com/prosemirror/prosemirror/tree/master/LICENSE).

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

function compareDeep(a, b) {
export function compareDeep(a, b) {
if (a === b) return true

@@ -16,2 +16,1 @@ if (!(a && typeof a == "object") ||

}
exports.compareDeep = compareDeep

@@ -1,456 +0,362 @@

const {Fragment} = require("./fragment")
const {Mark} = require("./mark")
import {Fragment} from "./fragment"
class ContentExpr {
constructor(nodeType, elements, inlineContent) {
this.nodeType = nodeType
this.elements = elements
this.inlineContent = inlineContent
// ::- Instances of this class represent a match state of a node
// type's [content expression](#model.NodeSpec.content), and can be
// used to find out whether further content matches here, and whether
// a given position is a valid end of the node.
export class ContentMatch {
constructor(validEnd) {
// :: bool
// True when this match state represents a valid end of the node.
this.validEnd = validEnd
this.next = []
this.wrapCache = []
}
get isLeaf() {
return this.elements.length == 0
static parse(string, nodeTypes) {
let stream = new TokenStream(string, nodeTypes)
if (stream.next == null) return ContentMatch.empty
let expr = parseExpr(stream)
if (stream.next) stream.err("Unexpected trailing text")
let match = dfa(nfa(expr))
checkForDeadEnds(match, stream)
return match
}
// : (?Object) → ContentMatch
// The content match at the start of this expression.
start(attrs) {
return new ContentMatch(this, attrs, 0, 0)
// :: (NodeType) → ?ContentMatch
// Match a node type and marks, returning a match after that node
// if successful.
matchType(type) {
for (let i = 0; i < this.next.length; i += 2)
if (this.next[i] == type) return this.next[i + 1]
return null
}
// : (NodeType, ?Object, ?Object) → ?ContentMatch
// Try to find a match that matches the given node, anywhere in the
// expression. (Useful when synthesizing a match for a node that's
// open to the left.)
atType(parentAttrs, type, attrs, marks = Mark.none) {
for (let i = 0; i < this.elements.length; i++)
if (this.elements[i].matchesType(type, attrs, marks, parentAttrs, this))
return new ContentMatch(this, parentAttrs, i, 0)
// :: (Fragment, ?number, ?number) → ?ContentMatch
// Try to match a fragment. Returns the resulting match when
// successful.
matchFragment(frag, start = 0, end = frag.childCount) {
let cur = this
for (let i = start; cur && i < end; i++)
cur = cur.matchType(frag.child(i).type)
return cur
}
matches(attrs, fragment, from, to) {
return this.start(attrs).matchToEnd(fragment, from, to)
get inlineContent() {
let first = this.next[0]
return first ? first.isInline : false
}
// Get a position in a known-valid fragment. If this is a simple
// (single-element) expression, we don't have to do any matching,
// and can simply skip to the position with count `index`.
getMatchAt(attrs, fragment, index = fragment.childCount) {
if (this.elements.length == 1)
return new ContentMatch(this, attrs, 0, index)
else
return this.start(attrs).matchFragment(fragment, 0, index)
get defaultType() {
return this.next[0]
}
checkReplace(attrs, content, from, to, replacement = Fragment.empty, start = 0, end = replacement.childCount) {
// Check for simple case, where the expression only has a single element
// (Optimization to avoid matching more than we need)
if (this.elements.length == 1) {
let elt = this.elements[0]
if (!checkCount(elt, content.childCount - (to - from) + (end - start), attrs, this)) return false
for (let i = start; i < end; i++) if (!elt.matches(replacement.child(i), attrs, this)) return false
return true
}
let match = this.getMatchAt(attrs, content, from).matchFragment(replacement, start, end)
return match ? match.matchToEnd(content, to) : false
compatible(other) {
for (let i = 0; i < this.next.length; i += 2)
for (let j = 0; j < other.next.length; j += 2)
if (this.next[i] == other.next[j]) return true
return false
}
checkReplaceWith(attrs, content, from, to, type, typeAttrs, marks) {
if (this.elements.length == 1) {
let elt = this.elements[0]
if (!checkCount(elt, content.childCount - (to - from) + 1, attrs, this)) return false
return elt.matchesType(type, typeAttrs, marks, attrs, this)
// :: (Fragment, bool, ?number) → ?Fragment
// Try to match the given fragment, and if that fails, see if it can
// be made to match by inserting nodes in front of it. When
// successful, return a fragment of inserted nodes (which may be
// empty if nothing had to be inserted). When `toEnd` is true, only
// return a fragment if the resulting match goes to the end of the
// content expression.
fillBefore(after, toEnd = false, startIndex = 0) {
let seen = [this]
function search(match, types) {
let finished = match.matchFragment(after, startIndex)
if (finished && (!toEnd || finished.validEnd))
return Fragment.from(types.map(tp => tp.createAndFill()))
for (let i = 0; i < match.next.length; i += 2) {
let type = match.next[i], next = match.next[i + 1]
if (!type.hasRequiredAttrs() && seen.indexOf(next) == -1) {
seen.push(next)
let found = search(next, types.concat(type))
if (found) return found
}
}
}
let match = this.getMatchAt(attrs, content, from).matchType(type, typeAttrs, marks)
return match ? match.matchToEnd(content, to) : false
return search(this, [])
}
compatible(other) {
for (let i = 0; i < this.elements.length; i++) {
let elt = this.elements[i]
for (let j = 0; j < other.elements.length; j++)
if (other.elements[j].compatible(elt)) return true
}
return false
// :: (NodeType) → ?[NodeType]
// Find a set of wrapping node types that would allow a node of the
// given type to appear at this position. The result may be empty
// (when it fits directly) and will be null when no such wrapping
// exists.
findWrapping(target) {
for (let i = 0; i < this.wrapCache.length; i += 2)
if (this.wrapCache[i] == target) return this.wrapCache[i + 1]
let computed = this.computeWrapping(target)
this.wrapCache.push(target, computed)
return computed
}
generateContent(attrs) {
return this.start(attrs).fillBefore(Fragment.empty, true)
}
static parse(nodeType, expr) {
let elements = [], pos = 0, inline = null
for (;;) {
pos += /^\s*/.exec(expr.slice(pos))[0].length
if (pos == expr.length) break
let types = /^(?:(\w+)|\(\s*(\w+(?:\s*\|\s*\w+)*)\s*\))/.exec(expr.slice(pos))
if (!types) throw new SyntaxError("Invalid content expression '" + expr + "' at " + pos)
pos += types[0].length
let attrs = /^\[([^\]]+)\]/.exec(expr.slice(pos))
if (attrs) pos += attrs[0].length
let marks = /^<(?:(_)|\s*(\w+(?:\s+\w+)*)\s*)>/.exec(expr.slice(pos))
if (marks) pos += marks[0].length
let repeat = /^(?:([+*?])|\{\s*(\d+|\.\w+)\s*(,\s*(\d+|\.\w+)?)?\s*\})/.exec(expr.slice(pos))
if (repeat) pos += repeat[0].length
let nodeTypes = expandTypes(nodeType.schema, types[1] ? [types[1]] : types[2].split(/\s*\|\s*/))
for (let i = 0; i < nodeTypes.length; i++) {
if (inline == null) inline = nodeTypes[i].isInline
else if (inline != nodeTypes[i].isInline) throw new SyntaxError("Mixing inline and block content in a single node")
computeWrapping(target) {
let seen = Object.create(null), active = [{match: this, type: null, via: null}]
while (active.length) {
let current = active.shift(), match = current.match
if (match.matchType(target)) {
let result = []
for (let obj = current; obj.type; obj = obj.via)
result.push(obj.type)
return result.reverse()
}
let attrSet = !attrs ? null : parseAttrs(nodeType, attrs[1])
let markSet = !marks ? false : marks[1] ? true : this.gatherMarks(nodeType.schema, marks[2].split(/\s+/))
let {min, max} = parseRepeat(nodeType, repeat)
if (min != 0 && (nodeTypes[0].hasRequiredAttrs(attrSet) || nodeTypes[0].isText))
throw new SyntaxError("Node type " + types[0] + " in type " + nodeType.name +
" is required, but has non-optional attributes")
let newElt = new ContentElement(nodeTypes, attrSet, markSet, min, max)
for (let i = elements.length - 1; i >= 0; i--) {
let prev = elements[i]
if (prev.min != prev.max && prev.overlaps(newElt))
throw new SyntaxError("Possibly ambiguous overlapping adjacent content expressions in '" + expr + "'")
if (prev.min != 0) break
for (let i = 0; i < match.next.length; i += 2) {
let type = match.next[i]
if (!type.isLeaf && !(type.name in seen) && (!current.type || match.next[i + 1].validEnd)) {
active.push({match: type.contentMatch, type, via: current})
seen[type.name] = true
}
}
elements.push(newElt)
}
return new ContentExpr(nodeType, elements, !!inline)
}
static gatherMarks(schema, marks) {
let found = []
for (let i = 0; i < marks.length; i++) {
let name = marks[i], mark = schema.marks[name], ok = mark
if (mark) {
found.push(mark)
} else {
for (let prop in schema.marks) {
let mark = schema.marks[prop]
if (name == "_" || (mark.spec.group && mark.spec.group.split(" ").indexOf(name) > -1))
found.push(ok = mark)
}
}
if (!ok) throw new SyntaxError("Unknown mark type: '" + marks[i] + "'")
toString() {
let seen = []
function scan(m) {
seen.push(m)
for (let i = 1; i < m.next.length; i += 2)
if (seen.indexOf(m.next[i]) == -1) scan(m.next[i])
}
return found
scan(this)
return seen.map((m, i) => {
let out = i + (m.validEnd ? "*" : " ") + " "
for (let i = 0; i < m.next.length; i += 2)
out += (i ? ", " : "") + m.next[i].name + "->" + seen.indexOf(m.next[i + 1])
return out
}).join("\n")
}
}
exports.ContentExpr = ContentExpr
class ContentElement {
constructor(nodeTypes, attrs, marks, min, max) {
ContentMatch.empty = new ContentMatch(true)
class TokenStream {
constructor(string, nodeTypes) {
this.string = string
this.nodeTypes = nodeTypes
this.attrs = attrs
this.marks = marks
this.min = min
this.max = max
this.inline = null
this.pos = 0
this.tokens = string.split(/\s*(?=\b|\W|$)/)
if (this.tokens[this.tokens.length - 1] == "") this.tokens.pop()
if (this.tokens[0] == "") this.tokens.unshift()
}
matchesType(type, attrs, marks, parentAttrs, parentExpr) {
if (this.nodeTypes.indexOf(type) == -1) return false
if (this.attrs) {
if (!attrs) return false
for (let prop in this.attrs)
if (attrs[prop] != resolveValue(this.attrs[prop], parentAttrs, parentExpr)) return false
}
if (this.marks === true) return true
if (this.marks === false) return marks.length == 0
for (let i = 0; i < marks.length; i++)
if (this.marks.indexOf(marks[i].type) == -1) return false
return true
}
get next() { return this.tokens[this.pos] }
matches(node, parentAttrs, parentExpr) {
return this.matchesType(node.type, node.attrs, node.marks, parentAttrs, parentExpr)
}
eat(tok) { return this.next == tok && (this.pos++ || true) }
compatible(other) {
for (let i = 0; i < this.nodeTypes.length; i++)
if (other.nodeTypes.indexOf(this.nodeTypes[i]) != -1) return true
return false
}
err(str) { throw new SyntaxError(str + " (in content expression '" + this.string + "')") }
}
constrainedAttrs(parentAttrs, expr) {
if (!this.attrs) return null
let attrs = Object.create(null)
for (let prop in this.attrs)
attrs[prop] = resolveValue(this.attrs[prop], parentAttrs, expr)
return attrs
}
function parseExpr(stream) {
let exprs = []
do { exprs.push(parseExprSeq(stream)) }
while (stream.eat("|"))
return exprs.length == 1 ? exprs[0] : {type: "choice", exprs}
}
createFiller(parentAttrs, expr) {
let type = this.nodeTypes[0], attrs = type.computeAttrs(this.constrainedAttrs(parentAttrs, expr))
return type.create(attrs, type.contentExpr.generateContent(attrs))
}
function parseExprSeq(stream) {
let exprs = []
do { exprs.push(parseExprSubscript(stream)) }
while (stream.next && stream.next != ")" && stream.next != "|")
return exprs.length == 1 ? exprs[0] : {type: "seq", exprs}
}
defaultType() {
let first = this.nodeTypes[0]
if (!(first.hasRequiredAttrs() || first.isText)) return first
function parseExprSubscript(stream) {
let expr = parseExprAtom(stream)
for (;;) {
if (stream.eat("+"))
expr = {type: "plus", expr}
else if (stream.eat("*"))
expr = {type: "star", expr}
else if (stream.eat("?"))
expr = {type: "opt", expr}
else if (stream.eat("{"))
expr = parseExprRange(stream, expr)
else break
}
return expr
}
overlaps(other) {
return this.nodeTypes.some(t => other.nodeTypes.indexOf(t) > -1)
}
function parseNum(stream) {
if (/\D/.test(stream.next)) stream.err("Expected number, got '" + stream.next + "'")
let result = Number(stream.next)
stream.pos++
return result
}
allowsMark(markType) {
return this.marks === true || this.marks && this.marks.indexOf(markType) > -1
function parseExprRange(stream, expr) {
let min = parseNum(stream), max = min
if (stream.eat(",")) {
if (stream.next != "}") max = parseNum(stream)
else max = -1
}
if (!stream.eat("}")) stream.err("Unclosed braced range")
return {type: "range", min, max, expr}
}
// ::- Represents a partial match of a node type's [content
// expression](#model.NodeSpec), and can be used to find out whether further
// content matches here, and whether a given position is a valid end
// of the parent node.
class ContentMatch {
constructor(expr, attrs, index, count) {
this.expr = expr
this.attrs = attrs
this.index = index
this.count = count
function resolveName(stream, name) {
let types = stream.nodeTypes, type = types[name]
if (type) return [type]
let result = []
for (let typeName in types) {
let type = types[typeName]
if (type.groups.indexOf(name) > -1) result.push(type)
}
if (result.length == 0) stream.err("No node type or group '" + name + "' found")
return result
}
get element() { return this.expr.elements[this.index] }
get nextElement() {
for (let i = this.index, count = this.count; i < this.expr.elements.length; i++) {
let element = this.expr.elements[i]
if (this.resolveValue(element.max) > count) return element
count = 0
}
function parseExprAtom(stream) {
if (stream.eat("(")) {
let expr = parseExpr(stream)
if (!stream.eat(")")) stream.err("Missing closing paren")
return expr
} else if (!/\W/.test(stream.next)) {
let exprs = resolveName(stream, stream.next).map(type => {
if (stream.inline == null) stream.inline = type.isInline
else if (stream.inline != type.isInline) stream.err("Mixing inline and block content")
return {type: "name", value: type}
})
stream.pos++
return exprs.length == 1 ? exprs[0] : {type: "choice", exprs}
} else {
stream.err("Unexpected token '" + stream.next + "'")
}
}
move(index, count) {
return new ContentMatch(this.expr, this.attrs, index, count)
}
// The code below helps compile a regular-expression-like language
// into a deterministic finite automaton. For a good introduction to
// these concepts, see https://swtch.com/~rsc/regexp/regexp1.html
resolveValue(value) {
return value instanceof AttrValue ? resolveValue(value, this.attrs, this.expr) : value
}
// : (Object) → [[{term: ?any, to: number}]]
// Construct an NFA from an expression as returned by the parser. The
// NFA is represented as an array of states, which are themselves
// arrays of edges, which are `{term, to}` objects. The first state is
// the entry state and the last node is the success state.
//
// Note that unlike typical NFAs, the edge ordering in this one is
// significant, in that it is used to contruct filler content when
// necessary.
function nfa(expr) {
let nfa = [[]]
connect(compile(expr, 0), node())
return nfa
// :: (Node) → ?ContentMatch
// Match a node, returning a new match after the node if successful.
matchNode(node) {
return this.matchType(node.type, node.attrs, node.marks)
function node() { return nfa.push([]) - 1 }
function edge(from, to, term) {
let edge = {term, to}
nfa[from].push(edge)
return edge
}
function connect(edges, to) { edges.forEach(edge => edge.to = to) }
// :: (NodeType, ?Object, [Mark]) → ?ContentMatch
// Match a node type and marks, returning an match after that node
// if successful.
matchType(type, attrs, marks = Mark.none) {
for (let {index, count} = this; index < this.expr.elements.length; index++, count = 0) {
let elt = this.expr.elements[index], max = this.resolveValue(elt.max)
if (count < max && elt.matchesType(type, attrs, marks, this.attrs, this.expr)) {
count++
return this.move(index, count)
function compile(expr, from) {
if (expr.type == "choice") {
return expr.exprs.reduce((out, expr) => out.concat(compile(expr, from)), [])
} else if (expr.type == "seq") {
for (let i = 0;; i++) {
let next = compile(expr.exprs[i], from)
if (i == expr.exprs.length - 1) return next
connect(next, from = node())
}
if (count < this.resolveValue(elt.min)) return null
}
}
// :: (Fragment, ?number, ?number) → ?union<ContentMatch, bool>
// Try to match a fragment. Returns a new match when successful,
// `null` when it ran into a required element it couldn't fit, and
// `false` if it reached the end of the expression without
// matching all nodes.
matchFragment(fragment, from = 0, to = fragment.childCount) {
if (from == to) return this
let fragPos = from, end = this.expr.elements.length
for (var {index, count} = this; index < end; index++, count = 0) {
let elt = this.expr.elements[index], max = this.resolveValue(elt.max)
while (count < max && fragPos < to) {
if (elt.matches(fragment.child(fragPos), this.attrs, this.expr)) {
count++
if (++fragPos == to) return this.move(index, count)
} else {
break
}
} else if (expr.type == "star") {
let loop = node()
edge(from, loop)
connect(compile(expr.expr, loop), loop)
return [edge(loop)]
} else if (expr.type == "plus") {
let loop = node()
connect(compile(expr.expr, from), loop)
connect(compile(expr.expr, loop), loop)
return [edge(loop)]
} else if (expr.type == "opt") {
return [edge(from)].concat(compile(expr.expr, from))
} else if (expr.type == "range") {
let cur = from
for (let i = 0; i < expr.min; i++) {
let next = node()
connect(compile(expr.expr, cur), next)
cur = next
}
if (count < this.resolveValue(elt.min)) return null
}
return false
}
// :: (Fragment, ?number, ?number) → bool
// Returns true only if the fragment matches here, and reaches all
// the way to the end of the content expression.
matchToEnd(fragment, start, end) {
let matched = this.matchFragment(fragment, start, end)
return matched && matched.validEnd() || false
}
// :: () → bool
// Returns true if this position represents a valid end of the
// expression (no required content follows after it).
validEnd() {
for (let i = this.index, count = this.count; i < this.expr.elements.length; i++, count = 0)
if (count < this.resolveValue(this.expr.elements[i].min)) return false
return true
}
// :: (Fragment, bool, ?number) → ?Fragment
// Try to match the given fragment, and if that fails, see if it can
// be made to match by inserting nodes in front of it. When
// successful, return a fragment of inserted nodes (which may be
// empty if nothing had to be inserted). When `toEnd` is true, only
// return a fragment if the resulting match goes to the end of the
// content expression.
fillBefore(after, toEnd, startIndex) {
let added = [], match = this, index = startIndex || 0, end = this.expr.elements.length
for (;;) {
let fits = match.matchFragment(after, index)
if (fits && (!toEnd || fits.validEnd())) return Fragment.from(added)
if (fits === false) return null // Matched to end with content remaining
let elt = match.element
if (match.count < this.resolveValue(elt.min)) {
added.push(elt.createFiller(this.attrs, this.expr))
match = match.move(match.index, match.count + 1)
} else if (match.index < end) {
match = match.move(match.index + 1, 0)
} else if (after.childCount > index) {
return null
if (expr.max == -1) {
connect(compile(expr.expr, cur), cur)
} else {
return Fragment.from(added)
}
}
}
possibleContent() {
let found = []
for (let i = this.index, count = this.count; i < this.expr.elements.length; i++, count = 0) {
let elt = this.expr.elements[i], attrs = elt.constrainedAttrs(this.attrs, this.expr)
if (count < this.resolveValue(elt.max)) for (let j = 0; j < elt.nodeTypes.length; j++) {
let type = elt.nodeTypes[j]
if (!type.hasRequiredAttrs(attrs) && !type.isText) found.push({type, attrs})
}
if (this.resolveValue(elt.min) > count) break
}
return found
}
// :: (MarkType) → bool
// Check whether a node with the given mark type is allowed after
// this position.
allowsMark(markType) {
return this.element.allowsMark(markType)
}
// :: (NodeType, ?Object, ?[Mark]) → ?[{type: NodeType, attrs: Object}]
// Find a set of wrapping node types that would allow a node of type
// `target` with attributes `targetAttrs` to appear at this
// position. The result may be empty (when it fits directly) and
// will be null when no such wrapping exists.
findWrapping(target, targetAttrs, targetMarks) {
let seen = Object.create(null), first = {match: this, via: null}, active = [first]
while (active.length) {
let current = active.shift(), match = current.match
if (match.matchType(target, targetAttrs, targetMarks)) {
let result = []
for (let obj = current; obj != first; obj = obj.via)
result.push({type: obj.match.expr.nodeType, attrs: obj.match.attrs})
return result.reverse()
}
let possible = match.possibleContent()
for (let i = 0; i < possible.length; i++) {
let {type, attrs} = possible[i], fullAttrs = type.computeAttrs(attrs)
if (!type.isLeaf && !(type.name in seen) &&
(current == first || match.matchType(type, fullAttrs).validEnd())) {
active.push({match: type.contentExpr.start(fullAttrs), via: current})
seen[type.name] = true
for (let i = expr.min; i < expr.max; i++) {
let next = node()
edge(cur, next)
connect(compile(expr.expr, cur), next)
cur = next
}
}
return [edge(cur)]
} else if (expr.type == "name") {
return [edge(from, null, expr.value)]
}
}
// :: (Node) → ?[{type: NodeType, attrs: Object}]
// Call [`findWrapping`](#model.ContentMatch.findWrapping) with the
// properties of the given node.
findWrappingFor(node) {
return this.findWrapping(node.type, node.attrs, node.marks)
}
}
exports.ContentMatch = ContentMatch
class AttrValue {
constructor(attr) { this.attr = attr }
}
function cmp(a, b) { return a - b }
function parseValue(nodeType, value) {
if (value.charAt(0) == ".") {
let attr = value.slice(1)
if (!nodeType.attrs[attr]) throw new SyntaxError("Node type " + nodeType.name + " has no attribute " + attr)
return new AttrValue(attr)
} else {
return JSON.parse(value)
}
}
function nullFrom(nfa, node) {
let result = []
scan(node)
return result.sort(cmp)
function resolveValue(value, attrs, expr) {
if (!(value instanceof AttrValue)) return value
let attrVal = attrs && attrs[value.attr]
return attrVal !== undefined ? attrVal : expr.nodeType.defaultAttrs[value.attr]
}
function checkCount(elt, count, attrs, expr) {
return count >= resolveValue(elt.min, attrs, expr) &&
count <= resolveValue(elt.max, attrs, expr)
}
function expandTypes(schema, types) {
let result = []
types.forEach(type => {
let found = schema.nodes[type]
if (found) {
if (result.indexOf(found) == -1) result.push(found)
} else {
for (let name in schema.nodes) {
let nodeType = schema.nodes[name]
if (nodeType.groups.indexOf(type) > -1 && result.indexOf(nodeType) == -1)
found = result.push(nodeType)
}
function scan(node) {
result.push(node)
for (let a = nfa[node], i = 0; i < a.length; i++) {
let {term, to} = a[i]
if (!term && result.indexOf(to) == -1) scan(to)
}
if (!found)
throw new SyntaxError("Node type or group '" + type + "' does not exist")
})
return result
}
}
const many = 2e9 // Big number representable as a 32-bit int
// : ([[{term: ?any, to: number}]]) → ContentMatch
// Compiles an NFA as produced by `nfa` into a DFA, modeled as a set
// of state objects (`ContentMatch` instances) with transitions
// between them.
function dfa(nfa) {
let labeled = Object.create(null)
return explore(nullFrom(nfa, 0))
function parseRepeat(nodeType, match) {
let min = 1, max = 1
if (match) {
if (match[1] == "+") {
max = many
} else if (match[1] == "*") {
min = 0
max = many
} else if (match[1] == "?") {
min = 0
} else if (match[2]) {
min = parseValue(nodeType, match[2])
if (match[3])
max = match[4] ? parseValue(nodeType, match[4]) : many
else
max = min
function explore(states) {
let out = []
states.forEach(node => {
nfa[node].forEach(({term, to}) => {
if (!term) return
let known = out.indexOf(term), set = known > -1 && out[known + 1]
nullFrom(nfa, to).forEach(node => {
if (!set) out.push(term, set = [])
if (set.indexOf(node) == -1) set.push(node)
})
})
})
let state = labeled[states.join(",")] = new ContentMatch(states.indexOf(nfa.length - 1) > -1)
for (let i = 0; i < out.length; i += 2) {
let states = out[i + 1].sort(cmp)
state.next.push(out[i], labeled[states.join(",")] || explore(states))
}
if (max == 0 || min > max)
throw new SyntaxError("Invalid repeat count in '" + match[0] + "'")
return state
}
return {min, max}
}
function parseAttrs(nodeType, expr) {
let parts = expr.split(/\s*,\s*/)
let attrs = Object.create(null)
for (let i = 0; i < parts.length; i++) {
let match = /^(\w+)=(\w+|\"(?:\\.|[^\\])*\"|\.\w+)$/.exec(parts[i])
if (!match) throw new SyntaxError("Invalid attribute syntax: " + parts[i])
attrs[match[1]] = parseValue(nodeType, match[2])
function checkForDeadEnds(match, stream) {
for (let i = 0, work = [match]; i < work.length; i++) {
let state = work[i], dead = !state.validEnd, nodes = []
for (let j = 0; j < state.next.length; j += 2) {
let node = state.next[j], next = state.next[j + 1]
nodes.push(node.name)
if (dead && !state.next[j].hasRequiredAttrs()) dead = false
if (work.indexOf(next) == -1) work.push(next)
}
if (dead) stream.err("Only non-generatable nodes (" + nodes.join(", ") + ") after a match state")
}
return attrs
}

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

function findDiffStart(a, b, pos) {
export function findDiffStart(a, b, pos) {
for (let i = 0;; i++) {

@@ -23,5 +23,4 @@ if (i == a.childCount || i == b.childCount)

}
exports.findDiffStart = findDiffStart
function findDiffEnd(a, b, posA, posB) {
export function findDiffEnd(a, b, posA, posB) {
for (let iA = a.childCount, iB = b.childCount;;) {

@@ -53,2 +52,1 @@ if (iA == 0 || iB == 0)

}
exports.findDiffEnd = findDiffEnd

@@ -1,12 +0,14 @@

const {findDiffStart, findDiffEnd} = require("./diff")
import {findDiffStart, findDiffEnd} from "./diff"
// ::- Fragment is the type used to represent a node's collection of
// child nodes.
// ::- A fragment represents a node's collection of child nodes.
//
// Fragments are persistent data structures. That means you should
// _not_ mutate them or their content, but create new instances
// whenever needed. The API tries to make this easy.
class Fragment {
// Like nodes, fragments are persistent data structures, and you
// should not mutate them or their content. Rather, you create new
// instances whenever needed. The API tries to make this easy.
export class Fragment {
constructor(content, size) {
this.content = content
// :: number
// The size of the fragment, which is the total of the size of its
// content nodes.
this.size = size || 0

@@ -36,3 +38,3 @@ if (size == null) for (let i = 0; i < content.length; i++)

// Call the given callback for every descendant node. The callback
// may return `false` to prevent traversal of its child nodes.
// may return `false` to prevent traversal of a given node's children.
descendants(f) {

@@ -61,4 +63,4 @@ this.nodesBetween(0, this.size, f)

// :: (Fragment) → Fragment
// Create a new fragment containing the content of this fragment and
// `other`.
// Create a new fragment containing the combined content of this
// fragment and the other.
append(other) {

@@ -161,10 +163,2 @@ if (!other.size) return this

// :: (number) → number
// Get the offset at (size of children before) the given index.
offsetAt(index) {
let offset = 0
for (let i = 0; i < index; i++) offset += this.content[i].nodeSize
return offset
}
// :: (number) → ?Node

@@ -241,3 +235,3 @@ // Get the child node at the given index, if it exists.

// Build a fragment from an array of nodes. Ensures that adjacent
// text nodes with the same style are joined together.
// text nodes with the same marks are joined together.
static fromArray(array) {

@@ -271,3 +265,2 @@ if (!array.length) return Fragment.empty

}
exports.Fragment = Fragment

@@ -274,0 +267,0 @@ const found = {index: 0, offset: 0}

@@ -1,7 +0,9 @@

const {Fragment} = require("./fragment")
const {Slice} = require("./replace")
const {Mark} = require("./mark")
import {Fragment} from "./fragment"
import {Slice} from "./replace"
import {Mark} from "./mark"
// ParseOptions:: interface
// Set of options for parsing a DOM node.
// These are the options recognized by the
// [`parse`](#model.DOMParser.parse) and
// [`parseSlice`](#model.DOMParser.parseSlice) methods.
//

@@ -32,11 +34,10 @@ // preserveWhitespace:: ?union<bool, "full">

//
// topStart:: ?number
// Can be used to influence the content match at the start of
// the topnode. When given, should be a valid index into
// `topNode`.
// topMatch:: ?ContentMatch
// Provide the starting content match that content parsed into the
// top node is matched against.
//
// context:: ?ResolvedPos
// A set of additional node names to count as
// A set of additional nodes to count as
// [context](#model.ParseRule.context) when parsing, above the
// given [top node](#model.DOMParser.parse^options.topNode).
// given [top node](#model.ParseOptions.topNode).

@@ -58,4 +59,15 @@ // ParseRule:: interface

// A CSS property name to match. When given, this rule matches
// inline styles that list that property.
// inline styles that list that property. May also have the form
// `"property=value"`, in which case the rule only matches if the
// propery's value exactly matches the given value. (For more
// complicated filters, use [`getAttrs`](#model.ParseRule.getAttrs)
// and return undefined to indicate that the match failed.)
//
// priority:: ?number
// Can be used to change the order in which the parse rules in a
// schema are tried. Those with higher priority come first. Rules
// without a priority are counted as having priority 50. This
// property is only meaningful in a schema—when directly
// constructing a parser, the order of the rule array is used.
//
// context:: ?string

@@ -83,9 +95,2 @@ // When given, restricts this rule to only match when the current

//
// priority:: ?number
// Can be used to change the order in which the parse rules in a
// schema are tried. Those with higher priority come first. Rules
// without a priority are counted as having priority 50. This
// property is only meaningful in a schema—when directly
// constructing a parser, the order of the rule array is used.
//
// ignore:: ?bool

@@ -102,3 +107,3 @@ // When true, ignore content that matches this rule.

//
// getAttrs:: ?(union<dom.Node, string>) → ?union<bool, Object>
// getAttrs:: ?(union<dom.Node, string>) → ?union<Object, false>
// A function used to compute the attributes for the node or mark

@@ -113,3 +118,3 @@ // created by this rule. Can also be used to describe further

//
// contentElement:: ?string
// contentElement:: ?union<string, (dom.Node) → dom.Node>
// For `tag` rules that produce non-leaf nodes or marks, by default

@@ -119,8 +124,9 @@ // the content of the DOM element is parsed as content of the mark

// a CSS selector string that the parser must use to find the actual
// content element.
// content element, or a function that returns the actual content
// element to the parser.
//
// getContent:: ?(dom.Node) → Fragment
// Can be used to override the content of a matched node. Will be
// called, and its result used, instead of parsing the node's child
// nodes.
// Can be used to override the content of a matched node. When
// present, instead of parsing the node's child nodes, the result of
// this function is used.
//

@@ -137,3 +143,3 @@ // preserveWhitespace:: ?union<bool, "full">

// is defined by an array of [rules](#model.ParseRule).
class DOMParser {
export class DOMParser {
// :: (Schema, [ParseRule])

@@ -144,4 +150,7 @@ // Create a parser that targets the given schema, using the given

// :: Schema
// The schema into which the parser parses.
this.schema = schema
// :: [ParseRule]
// The set of [parse rules](#model.ParseRule) that the parser
// uses, in order of precedence.
this.rules = rules

@@ -182,3 +191,3 @@ this.tags = []

if (matches(dom, rule.tag) &&
(!rule.namespace || dom.namespaceURI == rule.namespace) &&
(rule.namespace === undefined || dom.namespaceURI == rule.namespace) &&
(!rule.context || context.matchesContext(rule.context))) {

@@ -198,16 +207,20 @@ if (rule.getAttrs) {

let rule = this.styles[i]
if (rule.style == prop && (!rule.context || context.matchesContext(rule.context))) {
if (rule.getAttrs) {
let result = rule.getAttrs(value)
if (result === false) continue
rule.attrs = result
}
return rule
if (rule.style.indexOf(prop) != 0 ||
rule.context && !context.matchesContext(rule.context) ||
// Test that the style string either precisely matches the prop,
// or has an '=' sign after the prop, followed by the given
// value.
rule.style.length > prop.length &&
(rule.style.charCodeAt(prop.length) != 61 || rule.style.slice(prop.length + 1) != value))
continue
if (rule.getAttrs) {
let result = rule.getAttrs(value)
if (result === false) continue
rule.attrs = result
}
return rule
}
}
// :: (Schema) → [ParseRule]
// Extract the parse rules listed in a schema's [node
// specs](#model.NodeSpec.parseDOM).
// : (Schema) → [ParseRule]
static schemaRules(schema) {

@@ -243,3 +256,4 @@ let result = []

// Construct a DOM parser using the parsing rules listed in a
// schema's [node specs](#model.NodeSpec.parseDOM).
// schema's [node specs](#model.NodeSpec.parseDOM), reordered by
// [priority](#model.ParseRule.priority).
static fromSchema(schema) {

@@ -250,3 +264,2 @@ return schema.cached.domParser ||

}
exports.DOMParser = DOMParser

@@ -282,3 +295,3 @@ // : Object<bool> The block-level tags in HTML5

this.solid = solid
this.match = match || (options & OPT_OPEN_LEFT ? null : type.contentExpr.start(attrs))
this.match = match || (options & OPT_OPEN_LEFT ? null : type.contentMatch)
this.options = options

@@ -288,17 +301,19 @@ this.content = []

findWrapping(type, attrs) {
findWrapping(node) {
if (!this.match) {
if (!this.type) return []
let found = this.type.contentExpr.atType(this.attrs, type, attrs)
if (!found) {
let start = this.type.contentExpr.start(this.attrs), wrap
if (wrap = start.findWrapping(type, attrs)) {
let fill = this.type.contentMatch.fillBefore(Fragment.from(node))
if (fill) {
this.match = this.type.contentMatch.matchFragment(fill)
} else {
let start = this.type.contentMatch, wrap
if (wrap = start.findWrapping(node.type)) {
this.match = start
return wrap
} else {
return null
}
}
if (found) this.match = found
else return null
}
return this.match.findWrapping(type, attrs)
return this.match.findWrapping(node.type)
}

@@ -333,3 +348,3 @@

topContext = new NodeContext(topNode.type, topNode.attrs, true,
topNode.contentMatchAt(options.topStart || 0), topOptions)
options.topMatch || topNode.type.contentMatch, topOptions)
else if (open)

@@ -376,3 +391,3 @@ topContext = new NodeContext(null, null, true, null, topOptions)

let top = this.top
if ((top.type && top.type.inlineContent) || /\S/.test(value)) {
if ((top.type ? top.type.inlineContent : top.content.length && top.content[0].isInline) || /\S/.test(value)) {
if (!(top.options & OPT_PRESERVE_WS)) {

@@ -460,2 +475,3 @@ value = value.replace(/\s+/g, " ")

if (typeof contentDOM == "string") contentDOM = dom.querySelector(contentDOM)
else if (typeof contentDOM == "function") contentDOM = contentDOM(dom)
if (!contentDOM) contentDOM = dom

@@ -490,13 +506,13 @@ this.findAround(dom, contentDOM, true)

// nodes that we're in.
findPlace(type, attrs) {
findPlace(node) {
let route, sync
for (let depth = this.open; depth >= 0; depth--) {
let node = this.nodes[depth]
let found = node.findWrapping(type, attrs)
let cx = this.nodes[depth]
let found = cx.findWrapping(node)
if (found && (!route || route.length > found.length)) {
route = found
sync = node
sync = cx
if (!found.length) break
}
if (node.solid) break
if (cx.solid) break
}

@@ -506,3 +522,3 @@ if (!route) return false

for (let i = 0; i < route.length; i++)
this.enterInner(route[i].type, route[i].attrs, false)
this.enterInner(route[i], null, false)
return true

@@ -518,12 +534,8 @@ }

}
if (this.findPlace(node.type, node.attrs)) {
if (this.findPlace(node)) {
this.closeExtra()
let top = this.top
if (top.match) {
let match = top.match.matchNode(node)
if (!match) {
node = node.mark(node.marks.filter(mark => top.match.allowsMark(mark.type)))
match = top.match.matchNode(node)
}
top.match = match
top.match = top.match.matchType(node.type)
if (top.type) node = node.mark(top.type.allowedMarks(node.marks))
}

@@ -538,3 +550,3 @@ top.content.push(node)

enter(type, attrs, preserveWS) {
let ok = this.findPlace(type, attrs)
let ok = this.findPlace(type.create(attrs))
if (ok) this.enterInner(type, attrs, true, preserveWS)

@@ -541,0 +553,0 @@ return ok

@@ -1,11 +0,11 @@

exports.Node = require("./node").Node
;({ResolvedPos: exports.ResolvedPos, NodeRange: exports.NodeRange} = require("./resolvedpos"))
exports.Fragment = require("./fragment").Fragment
;({Slice: exports.Slice, ReplaceError: exports.ReplaceError} = require("./replace"))
exports.Mark = require("./mark").Mark
export {Node} from "./node"
export {ResolvedPos, NodeRange} from "./resolvedpos"
export {Fragment} from "./fragment"
export {Slice, ReplaceError} from "./replace"
export {Mark} from "./mark"
;({Schema: exports.Schema, NodeType: exports.NodeType, MarkType: exports.MarkType} = require("./schema"))
;({ContentMatch: exports.ContentMatch} = require("./content"))
export {Schema, NodeType, MarkType} from "./schema"
export {ContentMatch} from "./content"
exports.DOMParser = require("./from_dom").DOMParser
exports.DOMSerializer = require("./to_dom").DOMSerializer
export {DOMParser} from "./from_dom"
export {DOMSerializer} from "./to_dom"

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

const {compareDeep} = require("./comparedeep")
import {compareDeep} from "./comparedeep"

@@ -9,3 +9,3 @@ // ::- A mark is a piece of information that can be attached to a node,

// attributes they have.
class Mark {
export class Mark {
constructor(type, attrs) {

@@ -23,5 +23,5 @@ // :: MarkType

// well, in the right position. If this mark is already in the set,
// the set itself is returned. If a mark of this type with different
// attributes is already in the set, a set in which it is replaced
// by this one is returned.
// the set itself is returned. If any marks that are set to be
// [exclusive](#model.MarkSpec.excludes) with this mark are present,
// those are replaced by this one.
addToSet(set) {

@@ -115,5 +115,4 @@ let copy, placed = false

}
exports.Mark = Mark
// :: [Mark] The empty set of marks.
Mark.none = []

@@ -1,6 +0,6 @@

const {Fragment} = require("./fragment")
const {Mark} = require("./mark")
const {Slice, replace} = require("./replace")
const {ResolvedPos} = require("./resolvedpos")
const {compareDeep} = require("./comparedeep")
import {Fragment} from "./fragment"
import {Mark} from "./mark"
import {Slice, replace} from "./replace"
import {ResolvedPos} from "./resolvedpos"
import {compareDeep} from "./comparedeep"

@@ -19,5 +19,5 @@ const emptyAttrs = Object.create(null)

//
// **Never** directly mutate the properties of a `Node` object. See
// [this guide](/docs/guides/doc/) for more information.
class Node {
// **Do not** directly mutate the properties of a `Node` object. See
// [the guide](/docs/guide/#doc) for more information.
export class Node {
constructor(type, attrs, content, marks) {

@@ -30,4 +30,4 @@ // :: NodeType

// An object mapping attribute names to values. The kind of
// attributes allowed and required are determined by the node
// type.
// attributes allowed and required are
// [determined](#model.NodeSpec.attrs) by the node type.
this.attrs = attrs

@@ -41,3 +41,3 @@

// The marks (things like whether it is emphasized or part of a
// link) associated with this node.
// link) applied to this node.
this.marks = marks || Mark.none

@@ -51,4 +51,4 @@ }

// The size of this node, as defined by the integer-based [indexing
// scheme](/docs/guides/doc/#indexing). For text nodes, this is the
// amount of characters. For other leaf nodes, it is one. And for
// scheme](/docs/guide/#doc.indexing). For text nodes, this is the
// amount of characters. For other leaf nodes, it is one. For
// non-leaf nodes, it is the size of the content plus two (the start

@@ -78,6 +78,7 @@ // and end token).

// Invoke a callback for all descendant nodes recursively between
// the given two positions that are relative to start of this node's content.
// The callback is invoked with the node, its parent-relative position,
// its parent node, and its child index. If the callback returns false,
// the current node's children will not be recursed over.
// the given two positions that are relative to start of this node's
// content. The callback is invoked with the node, its
// parent-relative position, its parent node, and its child index.
// When the callback returns false for a given node, that node's
// children will not be recursed over.
nodesBetween(from, to, f, pos = 0) {

@@ -88,4 +89,4 @@ this.content.nodesBetween(from, to, f, pos, this)

// :: ((node: Node, pos: number, parent: Node) → ?bool)
// Call the given callback for every descendant node. If doesn't
// descend into a child node when the callback returns `false`.
// Call the given callback for every descendant node. Doesn't
// descend into a node when the callback returns `false`.
descendants(f) {

@@ -120,3 +121,3 @@ this.nodesBetween(0, this.content.size, f)

// :: (Node) → bool
// Test whether two nodes represent the same content.
// Test whether two nodes represent the same piece of document.
eq(other) {

@@ -159,3 +160,3 @@ return this == other || (this.sameMarkup(other) && this.content.eq(other.content))

// Create a copy of this node with only the content between the
// given offsets. If `to` is not given, it defaults to the end of
// given positions. If `to` is not given, it defaults to the end of
// the node.

@@ -192,3 +193,3 @@ cut(from, to) {

// :: (number) → ?Node
// Find the node after the given position.
// Find the node starting at the given position.
nodeAt(pos) {

@@ -226,4 +227,4 @@ for (let node = this;;) {

// :: (number) → ResolvedPos
// Resolve the given position in the document, returning an object
// describing its path through the document.
// Resolve the given position in the document, returning an
// [object](#model.ResolvedPos) with information about its context.
resolve(pos) { return ResolvedPos.resolveCached(this, pos) }

@@ -274,4 +275,4 @@

// editable content. This is usually the same as `isLeaf`, but can
// be configured with the [`leaf` property](#model.NodeSpec.leaf) on
// a node's spec (typically when the node is displayed as an
// be configured with the [`atom` property](#model.NodeSpec.atom) on
// a node's spec (typically used when the node is displayed as an
// uneditable [node view](#view.NodeView)).

@@ -293,13 +294,17 @@ get isAtom() { return this.type.isAtom }

contentMatchAt(index) {
return this.type.contentExpr.getMatchAt(this.attrs, this.content, index)
return this.type.contentMatch.matchFragment(this.content, 0, index)
}
// :: (number, number, ?Fragment, ?number, ?number) → bool
// Test whether replacing the range `from` to `to` (by index) with
// the given replacement fragment (which defaults to the empty
// fragment) would leave the node's content valid. You can
// optionally pass `start` and `end` indices into the replacement
// fragment.
canReplace(from, to, replacement, start, end) {
return this.type.contentExpr.checkReplace(this.attrs, this.content, from, to, replacement, start, end)
// Test whether replacing the range between `from` and `to` (by
// child index) with the given replacement fragment (which defaults
// to the empty fragment) would leave the node's content valid. You
// can optionally pass `start` and `end` indices into the
// replacement fragment.
canReplace(from, to, replacement = Fragment.empty, start = 0, end = replacement.childCount) {
let one = this.contentMatchAt(from).matchFragment(replacement, start, end)
let two = one && one.matchFragment(this.content, to)
if (!two || !two.validEnd) return false
for (let i = start; i < end; i++) if (!this.type.allowsMarks(replacement.child(i).marks)) return false
return true
}

@@ -309,6 +314,8 @@

// Test whether replacing the range `from` to `to` (by index) with a
// node of the given type with the given attributes and marks would
// be valid.
canReplaceWith(from, to, type, attrs, marks) {
return this.type.contentExpr.checkReplaceWith(this.attrs, this.content, from, to, type, attrs, marks || Mark.none)
// node of the given type.
canReplaceWith(from, to, type, marks) {
if (marks && !this.type.allowsMarks(marks)) return false
let start = this.contentMatchAt(from).matchType(type)
let end = start && start.matchFragment(this.content, to)
return end ? end.validEnd : false
}

@@ -327,4 +334,3 @@

defaultContentType(at) {
let elt = this.contentMatchAt(at).nextElement
return elt && elt.defaultType()
return this.contentMatchAt(at).defaultType
}

@@ -336,3 +342,3 @@

check() {
if (!this.type.validContent(this.content, this.attrs))
if (!this.type.validContent(this.content))
throw new RangeError(`Invalid content for node ${this.type.name}: ${this.content.toString().slice(0, 50)}`)

@@ -367,5 +373,4 @@ this.content.forEach(node => node.check())

}
exports.Node = Node
class TextNode extends Node {
export class TextNode extends Node {
constructor(type, attrs, content, marks) {

@@ -388,3 +393,3 @@ super(type, attrs, null, marks)

mark(marks) {
return new TextNode(this.type, this.attrs, this.text, marks)
return marks == this.marks ? this : new TextNode(this.type, this.attrs, this.text, marks)
}

@@ -412,3 +417,2 @@

}
exports.TextNode = TextNode

@@ -415,0 +419,0 @@ function wrapMarks(marks, str) {

This module defines ProseMirror's content model, the data structures
used to represent and manipulate documents.
used to represent and work with documents.

@@ -7,3 +7,3 @@ ### Document Structure

A ProseMirror document is a tree. At each level, a [node](#model.Node)
tags the type of content that's there, and holds a
describes the type of the content, and holds a
[fragment](#model.Fragment) containing its children.

@@ -28,6 +28,6 @@

The schema to which a document must conform is another data structure.
It describes the [nodes](#model.NodeSpec) and [marks](#model.MarkSpec)
that may appear in a document, and the places at which they may
appear.
Every ProseMirror document conforms to a
[schema](/docs/guide/#schema), which describes the set of nodes and
marks that it is made out of, along with the relations between those,
such as which node may occur as a child node of which other nodes.

@@ -53,3 +53,3 @@ @Schema

(But note that you do _not_ need to have a DOM implementation loaded
to load this module.)
to use this module.)

@@ -56,0 +56,0 @@ @DOMParser

@@ -1,6 +0,6 @@

const {Fragment} = require("./fragment")
import {Fragment} from "./fragment"
// ::- Error type raised by [`Node.replace`](#model.Node.replace) when
// given an invalid replacement.
class ReplaceError extends Error {
export class ReplaceError extends Error {
constructor(message) {

@@ -12,11 +12,20 @@ super(message)

}
exports.ReplaceError = ReplaceError
// ::- A slice represents a piece cut out of a larger document. It
// stores not only a fragment, but also the depth up to which nodes on
// both side are 'open' / cut through.
class Slice {
// both side are ‘open’ (cut through).
export class Slice {
// :: (Fragment, number, number)
// Create a slice. When specifying a non-zero open depth, you must
// make sure that there are nodes of at least that depth at the
// appropriate side of the fragment—i.e. if the fragment is an empty
// paragraph node, `openStart` and `openEnd` can't be greater than
// 1.
//
// It is not necessary for the content of open nodes to conform to
// the schema's content constraints, though it should be a valid
// start/end/middle for such a node, depending on which sides are
// open.
constructor(content, openStart, openEnd) {
// :: Fragment The slice's content nodes.
// :: Fragment The slice's content.
this.content = content

@@ -81,3 +90,2 @@ // :: number The open depth at the start.

}
exports.Slice = Slice

@@ -109,3 +117,3 @@ function removeRange(content, from, to) {

function replace($from, $to, slice) {
export function replace($from, $to, slice) {
if (slice.openStart > $from.depth)

@@ -117,3 +125,2 @@ throw new ReplaceError("Inserted content deeper than insertion position")

}
exports.replace = replace

@@ -173,3 +180,3 @@ function replaceOuter($from, $to, slice, depth) {

function close(node, content) {
if (!node.type.validContent(content, node.attrs))
if (!node.type.validContent(content))
throw new ReplaceError("Invalid content for node " + node.type.name)

@@ -176,0 +183,0 @@ return node.copy(content)

@@ -1,7 +0,7 @@

const {Mark} = require("./mark")
import {Mark} from "./mark"
// ::- You'll often have to '[resolve](#model.Node.resolve)' a
// position to get the context you need. Objects of this class
// represent such a resolved position, providing various pieces of
// context information and helper methods.
// ::- You can [_resolve_](#model.Node.resolve) a position to get more
// information about it. Objects of this class represent such a
// resolved position, providing various pieces of context information,
// and some helper methods.
//

@@ -11,3 +11,3 @@ // Throughout this interface, methods that take an optional `depth`

// numbers as `this.depth + value`.
class ResolvedPos {
export class ResolvedPos {
constructor(pos, path, parentOffset) {

@@ -19,4 +19,4 @@ // :: number The position that was resolved.

// The number of levels the parent node is from the root. If this
// position points directly into the root, it is 0. If it points
// into a top-level paragraph, 1, and so on.
// position points directly into the root node, it is 0. If it
// points into a top-level paragraph, 1, and so on.
this.depth = path.length / 3 - 1

@@ -36,3 +36,3 @@ // :: number The offset this position has into its parent node.

// a position points into a text node, that node is not considered
// the parent—text nodes are 'flat' in this model.
// the parent—text nodes are ‘flat’ in this model, and have no content.
get parent() { return this.node(this.depth) }

@@ -80,4 +80,4 @@

// :: (?number) → number
// The (absolute) position directly before the node at the given
// level, or, when `level` is `this.depth + 1`, the original
// The (absolute) position directly before the wrapping node at the
// given level, or, when `level` is `this.depth + 1`, the original
// position.

@@ -91,5 +91,4 @@ before(depth) {

// :: (?number) → number
// The (absolute) position directly after the node at the given
// level, or, when `level` is `this.depth + 1`, the original
// position.
// The (absolute) position directly after the wrapping node at the
// given level, or the original position when `level` is `this.depth + 1`.
after(depth) {

@@ -129,8 +128,8 @@ depth = this.resolveDepth(depth)

// :: (?bool) → [Mark]
// :: () → [Mark]
// Get the marks at this position, factoring in the surrounding
// marks' [`inclusive`](#model.MarkSpec.inclusive) property. If the
// position is at the start of a non-empty node, or `after` is true,
// the marks of the node after it (if any) are returned.
marks(after) {
// position is at the start of a non-empty node, the marks of the
// node after it (if any) are returned.
marks() {
let parent = this.parent, index = this.index()

@@ -147,3 +146,3 @@

// the node after this position the main reference.
if ((after && other) || !main) { let tmp = main; main = other; other = tmp }
if (!main) { let tmp = main; main = other; other = tmp }

@@ -160,2 +159,20 @@ // Use all marks in the main node, except those that have

// :: () → ?[Mark]
// Get the marks after the current position, if any, except those
// that are non-inclusive and not present at position `$end`. This
// is mostly useful for getting the set of marks to preserve after a
// deletion. Will return `null` if this position is at the end of
// its parent node or its parent node isn't a textblock (in which
// case no marks should be preserved).
marksAcross($end) {
let after = this.parent.maybeChild(this.index())
if (!after || !after.isInline) return null
let marks = after.marks, next = $end.parent.maybeChild($end.index())
for (var i = 0; i < marks.length; i++)
if (marks[i].type.spec.inclusive === false && (!next || !marks[i].isInSet(next.marks)))
marks = marks[i--].removeFromSet(marks)
return marks
}
// :: (number) → number

@@ -175,6 +192,5 @@ // The depth up to which this position and the given (non-resolved)

// will be returned. If they point into different blocks, the range
// around those blocks or their ancestors in their common ancestor
// is returned. You can pass in an optional predicate that will be
// called with a parent node to see if a range into that parent is
// acceptable.
// around those blocks in their shared ancestor is returned. You can
// pass in an optional predicate that will be called with a parent
// node to see if a range into that parent is acceptable.
blockRange(other = this, pred) {

@@ -239,8 +255,8 @@ if (other.pos < this.pos) return other.blockRange(this)

}
exports.ResolvedPos = ResolvedPos
let resolveCache = [], resolveCachePos = 0, resolveCacheSize = 6
// ::- Represents a flat range of content.
class NodeRange {
// ::- Represents a flat range of content, i.e. one that starts and
// ends in the same node.
export class NodeRange {
// :: (ResolvedPos, ResolvedPos, number)

@@ -276,2 +292,1 @@ // Construct a node range. `$from` and `$to` should point into the

}
exports.NodeRange = NodeRange

@@ -1,7 +0,7 @@

const OrderedMap = require("orderedmap")
import OrderedMap from "orderedmap"
const {Node, TextNode} = require("./node")
const {Fragment} = require("./fragment")
const {Mark} = require("./mark")
const {ContentExpr} = require("./content")
import {Node, TextNode} from "./node"
import {Fragment} from "./fragment"
import {Mark} from "./mark"
import {ContentMatch} from "./content"

@@ -16,3 +16,3 @@ // For node types where all attrs have a default value (or which don't

let attr = attrs[attrName]
if (attr.default === undefined) return null
if (!attr.hasDefault) return null
defaults[attrName] = attr.default

@@ -27,10 +27,6 @@ }

let given = value && value[name]
if (given == null) {
if (given === undefined) {
let attr = attrs[name]
if (attr.default !== undefined)
given = attr.default
else if (attr.compute)
given = attr.compute()
else
throw new RangeError("No value supplied for attribute " + name)
if (attr.hasDefault) given = attr.default
else throw new RangeError("No value supplied for attribute " + name)
}

@@ -49,6 +45,6 @@ built[name] = given

// ::- Node types are objects allocated once per `Schema` and used to
// tag `Node` instances with a type. They contain information about
// the node type, such as its name and what kind of node it
// [tag](#model.Node.type) `Node` instances. They contain information
// about the node type, such as its name and what kind of node it
// represents.
class NodeType {
export class NodeType {
constructor(name, schema, spec) {

@@ -71,5 +67,17 @@ // :: string

this.defaultAttrs = defaultAttrs(this.attrs)
this.contentExpr = null
// :: ContentMatch
// The starting match of the node type's content expression.
this.contentMatch = null
// : ?[MarkType]
// The set of marks allowed in this node. `null` means all marks
// are allowed.
this.markSet = null
// :: bool
// True if this node type has inline content.
this.inlineContent = null
// :: bool
// True if this is a block type

@@ -90,11 +98,7 @@ this.isBlock = !(spec.inline || name == "text")

// content.
get isTextblock() { return this.isBlock && this.contentExpr.inlineContent }
get isTextblock() { return this.isBlock && this.inlineContent }
// :: bool
// True if this node type has inline content.
get inlineContent() { return this.contentExpr.inlineContent }
// :: bool
// True for node types that allow no content.
get isLeaf() { return this.contentExpr.isLeaf }
get isLeaf() { return this.contentMatch == ContentMatch.empty }

@@ -113,3 +117,3 @@ // :: bool

compatibleContent(other) {
return this == other || this.contentExpr.compatible(other.contentExpr)
return this == other || this.contentMatch.compatible(other.contentMatch)
}

@@ -140,7 +144,6 @@

createChecked(attrs, content, marks) {
attrs = this.computeAttrs(attrs)
content = Fragment.from(content)
if (!this.validContent(content, attrs))
if (!this.validContent(content))
throw new RangeError("Invalid content for node " + this.name)
return new Node(this, attrs, content, Mark.setFrom(marks))
return new Node(this, this.computeAttrs(attrs), content, Mark.setFrom(marks))
}

@@ -159,7 +162,7 @@

if (content.size) {
let before = this.contentExpr.start(attrs).fillBefore(content)
let before = this.contentMatch.fillBefore(content)
if (!before) return null
content = before.append(content)
}
let after = this.contentExpr.getMatchAt(attrs, content).fillBefore(Fragment.empty, true)
let after = this.contentMatch.matchFragment(content).fillBefore(Fragment.empty, true)
if (!after) return null

@@ -169,9 +172,42 @@ return new Node(this, attrs, content.append(after), Mark.setFrom(marks))

// :: (Fragment, ?Object) → bool
// :: (Fragment) → bool
// Returns true if the given fragment is valid content for this node
// type with the given attributes.
validContent(content, attrs) {
return this.contentExpr.matches(attrs, content)
validContent(content) {
let result = this.contentMatch.matchFragment(content)
if (!result || !result.validEnd) return false
for (let i = 0; i < content.childCount; i++)
if (!this.allowsMarks(content.child(i).marks)) return false
return true
}
// :: (MarkType) → bool
// Check whether the given mark type is allowed in this node.
allowsMarkType(markType) {
return this.markSet == null || this.markSet.indexOf(markType) > -1
}
// :: ([Mark]) → bool
// Test whether the given set of marks are allowed in this node.
allowsMarks(marks) {
if (this.markSet == null) return true
for (let i = 0; i < marks.length; i++) if (!this.allowsMarkType(marks[i])) return false
return true
}
// :: ([Mark]) → [Mark]
// Removes the marks that are not allowed in this node from the given set.
allowedMarks(marks) {
if (this.markSet == null) return marks
let copy
for (let i = 0; i < marks.length; i++) {
if (!this.allowsMarkType(marks[i])) {
if (!copy) copy = marks.slice(0, i)
} else if (copy) {
copy.push(marks[i])
}
}
return !copy ? marks : copy.length ? copy : Mark.empty
}
static compile(nodes, schema) {

@@ -189,3 +225,2 @@ let result = Object.create(null)

}
exports.NodeType = NodeType

@@ -196,8 +231,8 @@ // Attribute descriptors

constructor(options) {
this.hasDefault = Object.prototype.hasOwnProperty.call(options, "default")
this.default = options.default
this.compute = options.compute
}
get isRequired() {
return this.default === undefined && !this.compute
return !this.hasDefault
}

@@ -209,5 +244,6 @@ }

// ::- Like nodes, marks (which are associated with nodes to signify
// things like emphasis or being part of a link) are tagged with type
// objects, which are instantiated once per `Schema`.
class MarkType {
// things like emphasis or being part of a link) are
// [tagged](#model.Mark.type) with type objects, which are
// instantiated once per `Schema`.
export class MarkType {
constructor(name, rank, schema, spec) {

@@ -273,15 +309,20 @@ // :: string

}
exports.MarkType = MarkType
// SchemaSpec:: interface
// An object describing a schema, as passed to the `Schema`
// An object describing a schema, as passed to the [`Schema`](#model.Schema)
// constructor.
//
// nodes:: union<Object<NodeSpec>, OrderedMap<NodeSpec>>
// The node types in this schema. Maps names to `NodeSpec` objects
// describing the node to be associated with that name. Their order
// is significant
// The node types in this schema. Maps names to
// [`NodeSpec`](#model.NodeSpec) objects that describe the node type
// associated with that name. Their order is significant—it
// determines which [parse rules](#model.NodeSpec.parseDOM) take
// precedence by default, and which nodes come first in a given
// [group](#model.NodeSpec.group).
//
// marks:: ?union<Object<MarkSpec>, OrderedMap<MarkSpec>>
// The mark types that exist in this schema.
// The mark types that exist in this schema. The order in which they
// are provided determines the order in which [mark
// sets](#model.Mark.addToSet) are sorted and in which [parse
// rules](#model.MarkSpec.parseDOM) are tried.
//

@@ -296,12 +337,19 @@ // topNode:: ?string

// The content expression for this node, as described in the [schema
// guide](/docs/guides/schema/). When not given, the node does not allow
// any content.
// guide](/docs/guide/#schema.content_expressions). When not given,
// the node does not allow any content.
//
// marks:: ?string
// The marks that are allowed inside of this node. May be a
// space-separated string referring to mark names or groups, `"_"`
// to explicitly allow all marks, or `""` to disallow marks. When
// not given, nodes with inline content default to allowing all
// marks, other nodes default to not allowing marks.
//
// group:: ?string
// The group or space-separated groups to which this node belongs, as
// referred to in the content expressions for the schema.
// The group or space-separated groups to which this node belongs,
// which can be referred to in the content expressions for the
// schema.
//
// inline:: ?bool
// Should be set to a truthy value for inline nodes. (Implied for
// text nodes.)
// Should be set to true for inline nodes. (Implied for text nodes.)
//

@@ -317,4 +365,4 @@ // atom:: ?bool

// selectable:: ?bool
// Controls whether nodes of this type can be selected (as a [node
// selection](#state.NodeSelection)). Defaults to true for non-text
// Controls whether nodes of this type can be selected as a [node
// selection](#state.NodeSelection). Defaults to true for non-text
// nodes.

@@ -335,6 +383,6 @@ //

// whereas defining nodes persist and wrap the inserted content.
// Likewise, the the _inserted_ content, when not inserting into a
// textblock, the defining parents of the content are preserved.
// Typically, non-default-paragraph textblock types, and possible
// list items, are marked as defining.
// Likewise, in _inserted_ content the defining parents of the
// content are preserved when possible. Typically,
// non-default-paragraph textblock types, and possibly list items,
// are marked as defining.
//

@@ -345,3 +393,3 @@ // isolating:: ?bool

// backspacing or lifting, won't cross. An example of a node that
// should probably have this set is a table cell.
// should probably have this enabled is a table cell.
//

@@ -352,6 +400,6 @@ // toDOM:: ?(node: Node) → DOMOutputSpec

// [`DOMSerializer.fromSchema`](#model.DOMSerializer^fromSchema)).
// Should return an [array structure](#model.DOMOutputSpec) that
// describes the resulting DOM structure, with an optional number
// zero (“hole”) in it to indicate where the node's content should
// be inserted.
// Should return a DOM node or an [array
// structure](#model.DOMOutputSpec) that describes one, with an
// optional number zero (“hole”) in it to indicate where the node's
// content should be inserted.
//

@@ -378,3 +426,4 @@ // For text nodes, the default is to create a text DOM node. Though

// Whether this mark should be active when the cursor is positioned
// at the start or end boundary of the mark. Defaults to true.
// at its end (or at its start when that is also the start of the
// parent node). Defaults to true.
//

@@ -384,3 +433,3 @@ // excludes:: ?string

// be a space-separated strings naming other marks or groups of marks.
// When a mark is [added](#model.mark.addToSet) to a set, all marks
// When a mark is [added](#model.Mark.addToSet) to a set, all marks
// that it excludes are removed in the process. If the set contains

@@ -398,3 +447,3 @@ // any mark that excludes the new mark but is not, itself, excluded

// group:: ?string
// The group or space-separated groups to which this node belongs.
// The group or space-separated groups to which this mark belongs.
//

@@ -412,19 +461,20 @@ // toDOM:: ?(mark: Mark, inline: bool) → DOMOutputSpec

//
// Used to define attributes. Attributes that have no default or
// compute property must be provided whenever a node or mark of a type
// that has them is created.
// Used to [define](#model.NodeSpec.attrs) attributes on nodes or
// marks.
//
// The following fields are supported:
//
// default:: ?any
// The default value for this attribute, to choose when no
// explicit value is provided.
//
// compute:: ?() → any
// A function that computes a default value for the attribute.
// The default value for this attribute, to use when no explicit
// value is provided. Attributes that have no default must be
// provided whenever a node or mark of a type that has them is
// created.
// ::- A document schema.
class Schema {
let warnedAboutMarkSyntax = false
// ::- A document schema. Holds [node](#model.NodeType) and [mark
// type](#model.MarkType) objects for the nodes and marks that may
// occur in conforming documents, and provides functionality for
// creating and deserializing such documents.
export class Schema {
// :: (SchemaSpec)
// Construct a schema from a specification.
// Construct a schema from a schema [specification](#model.SchemaSpec).
constructor(spec) {

@@ -436,3 +486,3 @@ // :: SchemaSpec

// [`OrderedMap`](https://github.com/marijnh/orderedmap) instances
// (not raw objects or null).
// (not raw objects).
this.spec = {}

@@ -454,17 +504,23 @@ for (let prop in spec) this.spec[prop] = spec[prop]

throw new RangeError(prop + " can not be both a node and a mark")
let type = this.nodes[prop]
type.contentExpr = ContentExpr.parse(type, this.spec.nodes.get(prop).content || "")
let type = this.nodes[prop], contentExpr = type.spec.content || "", markExpr = type.spec.marks
let oldStyle = /<(.*?)>/.test(contentExpr)
if (oldStyle) {
if (!warnedAboutMarkSyntax && typeof console != "undefined" && console.warn) {
warnedAboutMarkSyntax = true
console.warn("Angle-bracket syntax for marks in content expressions is deprecated. Use the `marks` spec property instead.")
}
markExpr = oldStyle[1]
contentExpr = contentExpr.replace(/<(.*?)>/g, "")
}
type.contentMatch = ContentMatch.parse(contentExpr, this.nodes)
type.inlineContent = type.contentMatch.inlineContent
type.markSet = markExpr == "_" ? null :
markExpr ? gatherMarks(this, markExpr.split(" ")) :
markExpr == "" || !type.inlineContent ? [] : null
}
for (let prop in this.marks) {
let type = this.marks[prop], excl = type.spec.excludes
type.excluded = excl == null ? [type] : excl == "" ? [] : ContentExpr.gatherMarks(this, excl.split(" "))
type.excluded = excl == null ? [type] : excl == "" ? [] : gatherMarks(this, excl.split(" "))
}
// :: Object
// An object for storing whatever values modules may want to
// compute and cache per schema. (If you want to store something
// in it, try to use property names unlikely to clash.)
this.cached = Object.create(null)
this.cached.wrappings = Object.create(null)
this.nodeFromJSON = this.nodeFromJSON.bind(this)

@@ -477,2 +533,9 @@ this.markFromJSON = this.markFromJSON.bind(this)

this.topNodeType = this.nodes[this.spec.topNode || "doc"]
// :: Object
// An object for storing whatever values modules may want to
// compute and cache per schema. (If you want to store something
// in it, try to use property names unlikely to clash.)
this.cached = Object.create(null)
this.cached.wrappings = Object.create(null)
}

@@ -531,2 +594,19 @@

}
exports.Schema = Schema
function gatherMarks(schema, marks) {
let found = []
for (let i = 0; i < marks.length; i++) {
let name = marks[i], mark = schema.marks[name], ok = mark
if (mark) {
found.push(mark)
} else {
for (let prop in schema.marks) {
let mark = schema.marks[prop]
if (name == "_" || (mark.spec.group && mark.spec.group.split(" ").indexOf(name) > -1))
found.push(ok = mark)
}
}
if (!ok) throw new SyntaxError("Unknown mark type: '" + marks[i] + "'")
}
return found
}

@@ -6,16 +6,18 @@ // DOMOutputSpec:: interface

//
// An array describes a DOM element. The first element in the array
// should be a string, and is the name of the DOM element. If the
// second element is a non-Array, non-DOM node object, it is
// interpreted as an object providing the DOM element's attributes.
// Any elements after that (including the 2nd if it's not an attribute
// object) are interpreted as children of the DOM elements, and must
// either be valid `DOMOutputSpec` values, or the number zero.
// An array describes a DOM element. The first value in the array
// should be a string—the name of the DOM element. If the second
// element is plain object object, it is interpreted as an set of
// attributes for the element. Any elements after that (including the
// 2nd if it's not an attribute object) are interpreted as children of
// the DOM elements, and must either be valid `DOMOutputSpec` values,
// or the number zero.
//
// The number zero (pronounced “hole”) is used to indicate the place
// where a ProseMirror node's content should be inserted.
// where a node's child nodes should be inserted. It it occurs in an
// output spec, it should be the only child element in its parent
// node.
// ::- A DOM serializer knows how to convert ProseMirror nodes and
// marks of various types to DOM nodes.
class DOMSerializer {
export class DOMSerializer {
// :: (Object<(node: Node) → DOMOutputSpec>, Object<?(mark: Mark, inline: bool) → DOMOutputSpec>)

@@ -31,4 +33,6 @@ // Create a serializer. `nodes` should map node names to functions

// :: Object<(node: Node) → DOMOutputSpec>
// The node serialization functions.
this.nodes = nodes || {}
// :: Object<(mark: Mark) → DOMOutputSpec>
// :: Object<?(mark: Mark, inline: bool) → DOMOutputSpec>
// The mark serialization functions.
this.marks = marks || {}

@@ -74,3 +78,3 @@ }

// [`serializeFragment`](#model.DOMSerializer.serializeFragment) on
// its [`content`](#model.Node.content).
// its [content](#model.Node.content).
serializeNode(node, options = {}) {

@@ -98,3 +102,5 @@ return this.renderStructure(this.nodes[node.type.name](node), node, options)

// :: (dom.Document, DOMOutputSpec) → {dom: dom.Node, contentDOM: ?dom.Node}
// Render an [output spec](#model.DOMOutputSpec).
// Render an [output spec](#model.DOMOutputSpec) to a DOM node. If
// the spec has a hole (zero) in it, `contentDOM` will point at the
// node with the hole.
static renderSpec(doc, structure) {

@@ -153,3 +159,3 @@ if (typeof structure == "string")

// :: (Schema) → Object<(node: Node) → DOMOutputSpec>
// : (Schema) → Object<(node: Node) → DOMOutputSpec>
// Gather the serializers in a schema's node specs into an object.

@@ -163,3 +169,3 @@ // This can be useful as a base to build a custom serializer from.

// :: (Schema) → Object<(mark: Mark) → DOMOutputSpec>
// : (Schema) → Object<(mark: Mark) → DOMOutputSpec>
// Gather the serializers in a schema's mark specs into an object.

@@ -170,3 +176,2 @@ static marksFromSchema(schema) {

}
exports.DOMSerializer = DOMSerializer

@@ -173,0 +178,0 @@ function gatherToDOM(obj) {

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

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