prosemirror-model
Advanced tools
Comparing version 0.17.0 to 0.18.0
@@ -99,2 +99,4 @@ var ref = require("./fragment"); | ||
ContentExpr.parse = function (nodeType, expr, specs) { | ||
var this$1 = this; | ||
var elements = [], pos = 0, inline = null | ||
@@ -121,3 +123,3 @@ for (;;) { | ||
var attrSet = !attrs ? null : parseAttrs(nodeType, attrs[1]) | ||
var markSet = !marks ? false : marks[1] ? true : checkMarks(nodeType.schema, marks[2].split(/\s+/)) | ||
var markSet = !marks ? false : marks[1] ? true : this$1.gatherMarks(nodeType.schema, marks[2].split(/\s+/)) | ||
var ref = parseRepeat(nodeType, repeat); | ||
@@ -142,2 +144,20 @@ var min = ref.min; | ||
ContentExpr.gatherMarks = function (schema, marks) { | ||
var found = [] | ||
for (var i = 0; i < marks.length; i++) { | ||
var name = marks[i], mark = schema.marks[name], ok = mark | ||
if (mark) { | ||
found.push(mark) | ||
} else { | ||
for (var prop in schema.marks) { | ||
var mark$1 = schema.marks[prop] | ||
if (name == "_" || (mark$1.spec.group && mark$1.spec.group.split(" ").indexOf(name) > -1)) | ||
{ found.push(ok = mark$1) } | ||
} | ||
} | ||
if (!ok) { throw new SyntaxError("Unknown mark type: '" + marks[i] + "'") } | ||
} | ||
return found | ||
}; | ||
Object.defineProperties( ContentExpr.prototype, prototypeAccessors ); | ||
@@ -417,12 +437,2 @@ exports.ContentExpr = ContentExpr | ||
function checkMarks(schema, marks) { | ||
var found = [] | ||
for (var i = 0; i < marks.length; i++) { | ||
var mark = schema.marks[marks[i]] | ||
if (mark) { found.push(mark) } | ||
else { throw new SyntaxError("Unknown mark type: '" + marks[i] + "'") } | ||
} | ||
return found | ||
} | ||
function resolveValue(value, attrs, expr) { | ||
@@ -429,0 +439,0 @@ if (!(value instanceof AttrValue)) { return value } |
@@ -20,2 +20,13 @@ var ref = require("./fragment"); | ||
// | ||
// context:: ?string | ||
// When given, restricts this rule to only match when the current | ||
// context—the parent nodes into which the content is being | ||
// parsed—matches this expression. Should contain one or more node | ||
// names followed by single or double slashes. For example | ||
// `"paragraph/"` means the rule only matches when the parent node | ||
// is a paragraph, `"blockquote/paragraph/"` restricts it to be in a | ||
// paragraph that is inside a blockquote, and `"section//"` matches | ||
// any position inside a section—a double slash matches any sequence | ||
// of ancestor nodes. | ||
// | ||
// node:: ?string | ||
@@ -118,5 +129,6 @@ // The name of the node type to create when this rule matches. Only | ||
// topNode:: ?Node | ||
// By default, the content is parsed into a `doc` node. You can | ||
// pass this option to use the type and attributes from a | ||
// different node as the top container. | ||
// By default, the content is parsed into the schema's default | ||
// [top node type](#model.Schema.topNodeType). You can pass this | ||
// option to use the type and attributes from a different node | ||
// as the top container. | ||
// | ||
@@ -127,2 +139,7 @@ // topStart:: ?number | ||
// `topNode`. | ||
// | ||
// context:: ?ResolvedPos | ||
// A set of additional node names to cound as | ||
// [context](#model.ParseRule.context) when parsing, above the | ||
// given [top node](#model.DOMParser.parse^options.topNode). | ||
DOMParser.prototype.parse = function (dom, options) { | ||
@@ -151,3 +168,3 @@ if ( options === void 0 ) options = {}; | ||
DOMParser.prototype.matchTag = function (dom) { | ||
DOMParser.prototype.matchTag = function (dom, context) { | ||
var this$1 = this; | ||
@@ -157,3 +174,3 @@ | ||
var rule = this$1.tags[i] | ||
if (matches(dom, rule.tag)) { | ||
if (matches(dom, rule.tag) && (!rule.context || context.matchesContext(rule.context))) { | ||
if (rule.getAttrs) { | ||
@@ -169,3 +186,3 @@ var result = rule.getAttrs(dom) | ||
DOMParser.prototype.matchStyle = function (prop, value) { | ||
DOMParser.prototype.matchStyle = function (prop, value, context) { | ||
var this$1 = this; | ||
@@ -175,3 +192,3 @@ | ||
var rule = this$1.styles[i] | ||
if (rule.style == prop) { | ||
if (rule.style == prop && (!rule.context || context.matchesContext(rule.context))) { | ||
if (rule.getAttrs) { | ||
@@ -305,3 +322,3 @@ var result = rule.getAttrs(value) | ||
else | ||
{ topContext = new NodeContext(parser.schema.nodes.doc, null, true, null, topOptions) } | ||
{ topContext = new NodeContext(parser.schema.topNodeType, null, true, null, topOptions) } | ||
this.nodes = [topContext] | ||
@@ -351,3 +368,3 @@ // : [Mark] The current set of marks | ||
// leading space. | ||
if (/^\s/.test(value)) { | ||
if (/^\s/.test(value) && this.open == this.nodes.length - 1) { | ||
var nodeBefore = top.content[top.content.length - 1] | ||
@@ -371,3 +388,3 @@ if (!nodeBefore || nodeBefore.isText && /\s$/.test(nodeBefore.text)) | ||
if (listTags.hasOwnProperty(name)) { normalizeList(dom) } | ||
var rule = (this.options.ruleFromNode && this.options.ruleFromNode(dom)) || this.parser.matchTag(dom) | ||
var rule = (this.options.ruleFromNode && this.options.ruleFromNode(dom)) || this.parser.matchTag(dom, this) | ||
if (rule ? rule.ignore : ignoreTags.hasOwnProperty(name)) { | ||
@@ -393,3 +410,3 @@ this.findInside(dom) | ||
for (var i = 0; i < styles.length; i += 2) { | ||
var rule = this$1.parser.matchStyle(styles[i], styles[i + 1]) | ||
var rule = this$1.parser.matchStyle(styles[i], styles[i + 1], this$1) | ||
if (!rule) { continue } | ||
@@ -600,2 +617,33 @@ if (rule.ignore) { ignore = true; break } | ||
// : (string) → bool | ||
// Determines whether the given [context | ||
// string](##ParseRule.context) matches this context. | ||
ParseContext.prototype.matchesContext = function (context) { | ||
var this$1 = this; | ||
var parts = context.split("/") | ||
var option = this.options.context | ||
var useRoot = !this.isOpen && (!option || option.parent.type == this.nodes[0].type) | ||
var minDepth = -(option ? option.depth + 1 : 0) + (useRoot ? 0 : 1) | ||
var match = function (i, depth) { | ||
for (; i >= 0; i--) { | ||
var part = parts[i] | ||
if (part == "") { | ||
if (i == parts.length - 1 || i == 0) { continue } | ||
for (; depth >= minDepth; depth--) | ||
{ if (match(i - 1, depth)) { return true } } | ||
return false | ||
} else { | ||
var name = depth > 0 || (depth == 0 && useRoot) ? this$1.nodes[depth].type.name | ||
: option && depth >= minDepth ? option.node(depth - minDepth).type.name | ||
: null | ||
if (name != part) { return false } | ||
depth-- | ||
} | ||
} | ||
return true | ||
} | ||
return match(parts.length - 1, this.open) | ||
}; | ||
Object.defineProperties( ParseContext.prototype, prototypeAccessors ); | ||
@@ -602,0 +650,0 @@ |
@@ -28,14 +28,22 @@ var ref = require("./comparedeep"); | ||
var copy, placed = false | ||
for (var i = 0; i < set.length; i++) { | ||
var other = set[i] | ||
if (other.type == this$1.type) { | ||
if (this$1.eq(other)) { return set } | ||
var copy = set.slice() | ||
copy[i] = this$1 | ||
return copy | ||
if (this$1.eq(other)) { return set } | ||
if (this$1.type.excludes(other.type)) { | ||
if (!copy) { copy = set.slice(0, i) } | ||
} else if (other.type.excludes(this$1.type)) { | ||
return set | ||
} else { | ||
if (!placed && other.type.rank > this$1.type.rank) { | ||
if (!copy) { copy = set.slice(0, i) } | ||
copy.push(this$1) | ||
placed = true | ||
} | ||
if (copy) { copy.push(other) } | ||
} | ||
if (other.type.rank > this$1.type.rank) | ||
{ return set.slice(0, i).concat(this$1).concat(set.slice(i)) } | ||
} | ||
return set.concat(this) | ||
if (!copy) { copy = set.slice() } | ||
if (!placed) { copy.push(this) } | ||
return copy | ||
}; | ||
@@ -69,6 +77,4 @@ | ||
Mark.prototype.eq = function (other) { | ||
if (this == other) { return true } | ||
if (this.type != other.type) { return false } | ||
if (!compareDeep(other.attrs, this.attrs)) { return false } | ||
return true | ||
return this == other || | ||
(this.type == other.type && compareDeep(this.attrs, other.attrs)) | ||
}; | ||
@@ -91,3 +97,5 @@ | ||
Mark.fromJSON = function (schema, json) { | ||
return schema.marks[json.type].create(json.attrs) | ||
var type = schema.marks[json.type] | ||
if (!type) { throw new RangeError(("There is no mark type " + (json.type) + " in this schema")) } | ||
return type.create(json.attrs) | ||
}; | ||
@@ -94,0 +102,0 @@ |
@@ -15,4 +15,2 @@ var ref = require("./fragment"); | ||
var warnedAboutMarksAt = false | ||
// ::- This class represents a node in the tree that makes up a | ||
@@ -51,3 +49,3 @@ // ProseMirror document. So a document is an instance of `Node`, with | ||
var prototypeAccessors = { nodeSize: {},childCount: {},textContent: {},firstChild: {},lastChild: {},isBlock: {},isTextblock: {},isInline: {},isText: {},isLeaf: {} }; | ||
var prototypeAccessors = { nodeSize: {},childCount: {},textContent: {},firstChild: {},lastChild: {},isBlock: {},isTextblock: {},isInline: {},isText: {},isLeaf: {},isAtom: {} }; | ||
@@ -246,10 +244,2 @@ // text:: ?string | ||
Node.prototype.marksAt = function (pos, useAfter) { | ||
if (!warnedAboutMarksAt && typeof console != "undefined" && console.warn) { | ||
warnedAboutMarksAt = true | ||
console.warn("Node.marksAt is deprecated. Use ResolvedPos.marks instead.") | ||
} | ||
return this.resolve(pos).marks(useAfter) | ||
}; | ||
// :: (?number, ?number, MarkType) → bool | ||
@@ -289,2 +279,10 @@ // Test whether a mark of the given type occurs in this document | ||
// :: bool | ||
// True when this is an atom, i.e. when it does not have directly | ||
// 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 | ||
// uneditable [node view](#view.NodeView)). | ||
prototypeAccessors.isAtom.get = function () { return this.type.isAtom }; | ||
// :: () → string | ||
@@ -339,2 +337,11 @@ // Return a string representation of this node for debugging | ||
// :: () | ||
// Check whether this node and its descendants conform to the | ||
// schema, and raise error when they do not. | ||
Node.prototype.check = function () { | ||
if (!this.type.validContent(this.content, this.attrs)) | ||
{ throw new RangeError(("Invalid content for node " + (this.type.name) + ": " + (this.content.toString().slice(0, 50)))) } | ||
this.content.forEach(function (node) { return node.check(); }) | ||
}; | ||
// :: () → Object | ||
@@ -362,3 +369,5 @@ // Return a JSON-serializeable representation of this node. | ||
if (json.type == "text") { return schema.text(json.text, marks) } | ||
return schema.nodeType(json.type).create(json.attrs, Fragment.fromJSON(schema, json.content), marks) | ||
var type = schema.nodeType(json.type) | ||
if (!type) { throw new RangeError(("There is no node type " + (json.type) + " in this schema")) } | ||
return type.create(json.attrs, Fragment.fromJSON(schema, json.content), marks) | ||
}; | ||
@@ -365,0 +374,0 @@ |
@@ -16,2 +16,7 @@ var ref = require("./fragment"); | ||
var prototypeAccessors = { name: {} }; | ||
prototypeAccessors.name.get = function () { return "ReplaceError" }; | ||
Object.defineProperties( ReplaceError.prototype, prototypeAccessors ); | ||
return ReplaceError; | ||
@@ -33,7 +38,7 @@ }(Error)); | ||
var prototypeAccessors = { size: {} }; | ||
var prototypeAccessors$1 = { size: {} }; | ||
// :: number | ||
// The size this slice would add when inserted into a document. | ||
prototypeAccessors.size.get = function () { | ||
prototypeAccessors$1.size.get = function () { | ||
return this.content.size - this.openLeft - this.openRight | ||
@@ -85,3 +90,3 @@ }; | ||
Object.defineProperties( Slice.prototype, prototypeAccessors ); | ||
Object.defineProperties( Slice.prototype, prototypeAccessors$1 ); | ||
exports.Slice = Slice | ||
@@ -88,0 +93,0 @@ |
@@ -82,3 +82,3 @@ var OrderedMap = require("orderedmap") | ||
var prototypeAccessors = { isInline: {},isTextblock: {},isLeaf: {} }; | ||
var prototypeAccessors = { isInline: {},isTextblock: {},isLeaf: {},isAtom: {} }; | ||
@@ -98,2 +98,7 @@ // :: bool | ||
// :: bool | ||
// True when this node is an atom, i.e. when it does not have | ||
// directly editable content. | ||
prototypeAccessors.isAtom.get = function () { return this.isLeaf || this.spec.atom }; | ||
NodeType.prototype.hasRequiredAttrs = function (ignore) { | ||
@@ -171,4 +176,6 @@ var this$1 = this; | ||
if (!result.doc) { throw new RangeError("Every schema needs a 'doc' type") } | ||
var topType = schema.spec.topNode || "doc" | ||
if (!result[topType]) { throw new RangeError("Schema is missing its top node type ('" + topType + "')") } | ||
if (!result.text) { throw new RangeError("Every schema needs a 'text' type") } | ||
for (var _ in result.text.attrs) { throw new RangeError("The text node type should not have attributes") } | ||
@@ -217,2 +224,3 @@ return result | ||
this.rank = rank | ||
this.excluded = null | ||
var defaults = defaultAttrs(this.attrs) | ||
@@ -257,2 +265,7 @@ this.instance = defaults && new Mark(this, defaults) | ||
}; | ||
// :: MarkType → bool | ||
MarkType.prototype.excludes = function (other) { | ||
return this.excluded.indexOf(other) > -1 | ||
}; | ||
exports.MarkType = MarkType | ||
@@ -271,2 +284,6 @@ | ||
// The mark types that exist in this schema. | ||
// | ||
// topNode:: ?string | ||
// The name of the default top-level node for the schema. Defaults | ||
// to `"doc"`. | ||
@@ -288,2 +305,7 @@ // NodeSpec:: interface | ||
// | ||
// atom:: ?bool | ||
// Can be set to true to indicate that, though this isn't a [leaf | ||
// node](#model.NodeType.isLeaf), it doesn't have directly editable | ||
// content and should be treated as a single unit in the view. | ||
// | ||
// attrs:: ?Object<AttributeSpec> | ||
@@ -345,2 +367,20 @@ // The attributes that nodes of this type get. | ||
// | ||
// excludes:: ?string | ||
// Determines which other marks this mark can coexist with. Should | ||
// 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 | ||
// that it excludes are removed in the process. If the set contains | ||
// any mark that excludes the new mark but is not, itself, excluded | ||
// by the new mark, the mark can not be added an the set. You can | ||
// use the value `"_"` to indicate that the mark excludes all | ||
// marks in the schema. | ||
// | ||
// Defaults to only being exclusive with marks of the same type. You | ||
// can set it to an empty string (or any string not containing the | ||
// mark's own name) to allow multiple marks of a given type to | ||
// coexist (as long as they have different attributes). | ||
// | ||
// group:: ?string | ||
// The group or space-separated groups to which this node belongs. | ||
// | ||
// toDOM:: ?(mark: Mark) → DOMOutputSpec | ||
@@ -374,21 +414,31 @@ // Defines the default way marks of this type should be serialized | ||
// :: OrderedMap<NodeSpec> The node specs that the schema is based on. | ||
this.nodeSpec = OrderedMap.from(spec.nodes) | ||
// :: OrderedMap<MarkSpec> The mark spec that the schema is based on. | ||
this.markSpec = OrderedMap.from(spec.marks) | ||
// :: SchemaSpec | ||
// The [spec](#model.SchemaSpec) on which the schema is based, | ||
// with the added guarantee that its `nodes` and `marks` | ||
// properties are | ||
// [`OrderedMap`](https://github.com/marijnh/orderedmap) instances | ||
// (not raw objects or null). | ||
this.spec = {} | ||
for (var prop in spec) { this$1.spec[prop] = spec[prop] } | ||
this.spec.nodes = OrderedMap.from(spec.nodes) | ||
this.spec.marks = OrderedMap.from(spec.marks) | ||
// :: Object<NodeType> | ||
// An object mapping the schema's node names to node type objects. | ||
this.nodes = NodeType.compile(this.nodeSpec, this) | ||
this.nodes = NodeType.compile(this.spec.nodes, this) | ||
// :: Object<MarkType> | ||
// A map from mark names to mark type objects. | ||
this.marks = MarkType.compile(this.markSpec, this) | ||
this.marks = MarkType.compile(this.spec.marks, this) | ||
for (var prop in this$1.nodes) { | ||
if (prop in this$1.marks) | ||
{ throw new RangeError(prop + " can not be both a node and a mark") } | ||
var type = this$1.nodes[prop] | ||
type.contentExpr = ContentExpr.parse(type, this$1.nodeSpec.get(prop).content || "", this$1.nodeSpec) | ||
for (var prop$1 in this$1.nodes) { | ||
if (prop$1 in this$1.marks) | ||
{ throw new RangeError(prop$1 + " can not be both a node and a mark") } | ||
var type = this$1.nodes[prop$1] | ||
type.contentExpr = ContentExpr.parse(type, this$1.spec.nodes.get(prop$1).content || "", this$1.spec.nodes) | ||
} | ||
for (var prop$2 in this$1.marks) { | ||
var type$1 = this$1.marks[prop$2], excl = type$1.spec.excludes | ||
type$1.excluded = excl == null ? [type$1] : excl == "" ? [] : ContentExpr.gatherMarks(this$1, excl.split(" ")) | ||
} | ||
@@ -404,4 +454,21 @@ // :: Object | ||
this.markFromJSON = this.markFromJSON.bind(this) | ||
// :: NodeType | ||
// The type of the [default top node](#model.SchemaSpec.topNode) | ||
// for this schema. | ||
this.topNodeType = this.nodes[this.spec.topNode || "doc"] | ||
}; | ||
var prototypeAccessors$2 = { nodeSpec: {},markSpec: {} }; | ||
prototypeAccessors$2.nodeSpec.get = function () { | ||
warnAboutSpec() | ||
return this.spec.nodes | ||
}; | ||
prototypeAccessors$2.markSpec.get = function () { | ||
warnAboutSpec() | ||
return this.spec.marks | ||
}; | ||
// :: (union<string, NodeType>, ?Object, ?union<Fragment, Node, [Node]>, ?[Mark]) → Node | ||
@@ -457,2 +524,12 @@ // Create a node in this schema. The `type` may be a string or a | ||
}; | ||
Object.defineProperties( Schema.prototype, prototypeAccessors$2 ); | ||
exports.Schema = Schema | ||
var warnedAboutSpec = false | ||
function warnAboutSpec() { | ||
if (!warnedAboutSpec && typeof console != "undefined" && console.warn) { | ||
warnedAboutSpec = true | ||
console.warn("The Schema properties .nodeSpec and .markSpec are deprecated. Use .spec.nodes and .spec.marks instead") | ||
} | ||
} |
{ | ||
"name": "prosemirror-model", | ||
"version": "0.17.0", | ||
"version": "0.18.0", | ||
"description": "ProseMirror's document model", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -105,3 +105,3 @@ const {Fragment} = require("./fragment") | ||
let attrSet = !attrs ? null : parseAttrs(nodeType, attrs[1]) | ||
let markSet = !marks ? false : marks[1] ? true : checkMarks(nodeType.schema, marks[2].split(/\s+/)) | ||
let markSet = !marks ? false : marks[1] ? true : this.gatherMarks(nodeType.schema, marks[2].split(/\s+/)) | ||
let {min, max} = parseRepeat(nodeType, repeat) | ||
@@ -123,2 +123,20 @@ if (min != 0 && (nodeTypes[0].hasRequiredAttrs(attrSet) || nodeTypes[0].isText)) | ||
} | ||
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] + "'") | ||
} | ||
return found | ||
} | ||
} | ||
@@ -376,12 +394,2 @@ exports.ContentExpr = ContentExpr | ||
function checkMarks(schema, marks) { | ||
let found = [] | ||
for (let i = 0; i < marks.length; i++) { | ||
let mark = schema.marks[marks[i]] | ||
if (mark) found.push(mark) | ||
else throw new SyntaxError("Unknown mark type: '" + marks[i] + "'") | ||
} | ||
return found | ||
} | ||
function resolveValue(value, attrs, expr) { | ||
@@ -388,0 +396,0 @@ if (!(value instanceof AttrValue)) return value |
@@ -17,2 +17,13 @@ const {Fragment} = require("./fragment") | ||
// | ||
// context:: ?string | ||
// When given, restricts this rule to only match when the current | ||
// context—the parent nodes into which the content is being | ||
// parsed—matches this expression. Should contain one or more node | ||
// names followed by single or double slashes. For example | ||
// `"paragraph/"` means the rule only matches when the parent node | ||
// is a paragraph, `"blockquote/paragraph/"` restricts it to be in a | ||
// paragraph that is inside a blockquote, and `"section//"` matches | ||
// any position inside a section—a double slash matches any sequence | ||
// of ancestor nodes. | ||
// | ||
// node:: ?string | ||
@@ -117,5 +128,6 @@ // The name of the node type to create when this rule matches. Only | ||
// topNode:: ?Node | ||
// By default, the content is parsed into a `doc` node. You can | ||
// pass this option to use the type and attributes from a | ||
// different node as the top container. | ||
// By default, the content is parsed into the schema's default | ||
// [top node type](#model.Schema.topNodeType). You can pass this | ||
// option to use the type and attributes from a different node | ||
// as the top container. | ||
// | ||
@@ -126,2 +138,7 @@ // topStart:: ?number | ||
// `topNode`. | ||
// | ||
// context:: ?ResolvedPos | ||
// A set of additional node names to cound as | ||
// [context](#model.ParseRule.context) when parsing, above the | ||
// given [top node](#model.DOMParser.parse^options.topNode). | ||
parse(dom, options = {}) { | ||
@@ -146,6 +163,6 @@ let context = new ParseContext(this, options, false) | ||
matchTag(dom) { | ||
matchTag(dom, context) { | ||
for (let i = 0; i < this.tags.length; i++) { | ||
let rule = this.tags[i] | ||
if (matches(dom, rule.tag)) { | ||
if (matches(dom, rule.tag) && (!rule.context || context.matchesContext(rule.context))) { | ||
if (rule.getAttrs) { | ||
@@ -161,6 +178,6 @@ let result = rule.getAttrs(dom) | ||
matchStyle(prop, value) { | ||
matchStyle(prop, value, context) { | ||
for (let i = 0; i < this.styles.length; i++) { | ||
let rule = this.styles[i] | ||
if (rule.style == prop) { | ||
if (rule.style == prop && (!rule.context || context.matchesContext(rule.context))) { | ||
if (rule.getAttrs) { | ||
@@ -295,3 +312,3 @@ let result = rule.getAttrs(value) | ||
else | ||
topContext = new NodeContext(parser.schema.nodes.doc, null, true, null, topOptions) | ||
topContext = new NodeContext(parser.schema.topNodeType, null, true, null, topOptions) | ||
this.nodes = [topContext] | ||
@@ -339,3 +356,3 @@ // : [Mark] The current set of marks | ||
// leading space. | ||
if (/^\s/.test(value)) { | ||
if (/^\s/.test(value) && this.open == this.nodes.length - 1) { | ||
let nodeBefore = top.content[top.content.length - 1] | ||
@@ -359,3 +376,3 @@ if (!nodeBefore || nodeBefore.isText && /\s$/.test(nodeBefore.text)) | ||
if (listTags.hasOwnProperty(name)) normalizeList(dom) | ||
let rule = (this.options.ruleFromNode && this.options.ruleFromNode(dom)) || this.parser.matchTag(dom) | ||
let rule = (this.options.ruleFromNode && this.options.ruleFromNode(dom)) || this.parser.matchTag(dom, this) | ||
if (rule ? rule.ignore : ignoreTags.hasOwnProperty(name)) { | ||
@@ -379,3 +396,3 @@ this.findInside(dom) | ||
for (let i = 0; i < styles.length; i += 2) { | ||
let rule = this.parser.matchStyle(styles[i], styles[i + 1]) | ||
let rule = this.parser.matchStyle(styles[i], styles[i + 1], this) | ||
if (!rule) continue | ||
@@ -565,2 +582,31 @@ if (rule.ignore) { ignore = true; break } | ||
} | ||
// : (string) → bool | ||
// Determines whether the given [context | ||
// string](##ParseRule.context) matches this context. | ||
matchesContext(context) { | ||
let parts = context.split("/") | ||
let option = this.options.context | ||
let useRoot = !this.isOpen && (!option || option.parent.type == this.nodes[0].type) | ||
let minDepth = -(option ? option.depth + 1 : 0) + (useRoot ? 0 : 1) | ||
let match = (i, depth) => { | ||
for (; i >= 0; i--) { | ||
let part = parts[i] | ||
if (part == "") { | ||
if (i == parts.length - 1 || i == 0) continue | ||
for (; depth >= minDepth; depth--) | ||
if (match(i - 1, depth)) return true | ||
return false | ||
} else { | ||
let name = depth > 0 || (depth == 0 && useRoot) ? this.nodes[depth].type.name | ||
: option && depth >= minDepth ? option.node(depth - minDepth).type.name | ||
: null | ||
if (name != part) return false | ||
depth-- | ||
} | ||
} | ||
return true | ||
} | ||
return match(parts.length - 1, this.open) | ||
} | ||
} | ||
@@ -567,0 +613,0 @@ |
@@ -26,14 +26,22 @@ const {compareDeep} = require("./comparedeep") | ||
addToSet(set) { | ||
for (var i = 0; i < set.length; i++) { | ||
var other = set[i] | ||
if (other.type == this.type) { | ||
if (this.eq(other)) return set | ||
let copy = set.slice() | ||
copy[i] = this | ||
return copy | ||
let copy, placed = false | ||
for (let i = 0; i < set.length; i++) { | ||
let other = set[i] | ||
if (this.eq(other)) return set | ||
if (this.type.excludes(other.type)) { | ||
if (!copy) copy = set.slice(0, i) | ||
} else if (other.type.excludes(this.type)) { | ||
return set | ||
} else { | ||
if (!placed && other.type.rank > this.type.rank) { | ||
if (!copy) copy = set.slice(0, i) | ||
copy.push(this) | ||
placed = true | ||
} | ||
if (copy) copy.push(other) | ||
} | ||
if (other.type.rank > this.type.rank) | ||
return set.slice(0, i).concat(this).concat(set.slice(i)) | ||
} | ||
return set.concat(this) | ||
if (!copy) copy = set.slice() | ||
if (!placed) copy.push(this) | ||
return copy | ||
} | ||
@@ -45,3 +53,3 @@ | ||
removeFromSet(set) { | ||
for (var i = 0; i < set.length; i++) | ||
for (let i = 0; i < set.length; i++) | ||
if (this.eq(set[i])) | ||
@@ -64,6 +72,4 @@ return set.slice(0, i).concat(set.slice(i + 1)) | ||
eq(other) { | ||
if (this == other) return true | ||
if (this.type != other.type) return false | ||
if (!compareDeep(other.attrs, this.attrs)) return false | ||
return true | ||
return this == other || | ||
(this.type == other.type && compareDeep(this.attrs, other.attrs)) | ||
} | ||
@@ -84,3 +90,5 @@ | ||
static fromJSON(schema, json) { | ||
return schema.marks[json.type].create(json.attrs) | ||
let type = schema.marks[json.type] | ||
if (!type) throw new RangeError(`There is no mark type ${json.type} in this schema`) | ||
return type.create(json.attrs) | ||
} | ||
@@ -104,3 +112,3 @@ | ||
if (marks instanceof Mark) return [marks] | ||
var copy = marks.slice() | ||
let copy = marks.slice() | ||
copy.sort((a, b) => a.type.rank - b.type.rank) | ||
@@ -107,0 +115,0 @@ return copy |
@@ -9,4 +9,2 @@ const {Fragment} = require("./fragment") | ||
let warnedAboutMarksAt = false | ||
// ::- This class represents a node in the tree that makes up a | ||
@@ -225,10 +223,2 @@ // ProseMirror document. So a document is an instance of `Node`, with | ||
marksAt(pos, useAfter) { | ||
if (!warnedAboutMarksAt && typeof console != "undefined" && console.warn) { | ||
warnedAboutMarksAt = true | ||
console.warn("Node.marksAt is deprecated. Use ResolvedPos.marks instead.") | ||
} | ||
return this.resolve(pos).marks(useAfter) | ||
} | ||
// :: (?number, ?number, MarkType) → bool | ||
@@ -268,2 +258,10 @@ // Test whether a mark of the given type occurs in this document | ||
// :: bool | ||
// True when this is an atom, i.e. when it does not have directly | ||
// 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 | ||
// uneditable [node view](#view.NodeView)). | ||
get isAtom() { return this.type.isAtom } | ||
// :: () → string | ||
@@ -318,2 +316,11 @@ // Return a string representation of this node for debugging | ||
// :: () | ||
// Check whether this node and its descendants conform to the | ||
// schema, and raise error when they do not. | ||
check() { | ||
if (!this.type.validContent(this.content, this.attrs)) | ||
throw new RangeError(`Invalid content for node ${this.type.name}: ${this.content.toString().slice(0, 50)}`) | ||
this.content.forEach(node => node.check()) | ||
} | ||
// :: () → Object | ||
@@ -339,3 +346,5 @@ // Return a JSON-serializeable representation of this node. | ||
if (json.type == "text") return schema.text(json.text, marks) | ||
return schema.nodeType(json.type).create(json.attrs, Fragment.fromJSON(schema, json.content), marks) | ||
let type = schema.nodeType(json.type) | ||
if (!type) throw new RangeError(`There is no node type ${json.type} in this schema`) | ||
return type.create(json.attrs, Fragment.fromJSON(schema, json.content), marks) | ||
} | ||
@@ -342,0 +351,0 @@ } |
@@ -10,2 +10,3 @@ const {Fragment} = require("./fragment") | ||
} | ||
get name() { return "ReplaceError" } | ||
} | ||
@@ -12,0 +13,0 @@ exports.ReplaceError = ReplaceError |
@@ -91,2 +91,7 @@ const OrderedMap = require("orderedmap") | ||
// :: bool | ||
// True when this node is an atom, i.e. when it does not have | ||
// directly editable content. | ||
get isAtom() { return this.isLeaf || this.spec.atom } | ||
hasRequiredAttrs(ignore) { | ||
@@ -162,4 +167,6 @@ for (let n in this.attrs) | ||
if (!result.doc) throw new RangeError("Every schema needs a 'doc' type") | ||
let topType = schema.spec.topNode || "doc" | ||
if (!result[topType]) throw new RangeError("Schema is missing its top node type ('" + topType + "')") | ||
if (!result.text) throw new RangeError("Every schema needs a 'text' type") | ||
for (let _ in result.text.attrs) throw new RangeError("The text node type should not have attributes") | ||
@@ -206,2 +213,3 @@ return result | ||
this.rank = rank | ||
this.excluded = null | ||
let defaults = defaultAttrs(this.attrs) | ||
@@ -242,2 +250,7 @@ this.instance = defaults && new Mark(this, defaults) | ||
} | ||
// :: MarkType → bool | ||
excludes(other) { | ||
return this.excluded.indexOf(other) > -1 | ||
} | ||
} | ||
@@ -257,2 +270,6 @@ exports.MarkType = MarkType | ||
// The mark types that exist in this schema. | ||
// | ||
// topNode:: ?string | ||
// The name of the default top-level node for the schema. Defaults | ||
// to `"doc"`. | ||
@@ -274,2 +291,7 @@ // NodeSpec:: interface | ||
// | ||
// atom:: ?bool | ||
// Can be set to true to indicate that, though this isn't a [leaf | ||
// node](#model.NodeType.isLeaf), it doesn't have directly editable | ||
// content and should be treated as a single unit in the view. | ||
// | ||
// attrs:: ?Object<AttributeSpec> | ||
@@ -331,2 +353,20 @@ // The attributes that nodes of this type get. | ||
// | ||
// excludes:: ?string | ||
// Determines which other marks this mark can coexist with. Should | ||
// 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 | ||
// that it excludes are removed in the process. If the set contains | ||
// any mark that excludes the new mark but is not, itself, excluded | ||
// by the new mark, the mark can not be added an the set. You can | ||
// use the value `"_"` to indicate that the mark excludes all | ||
// marks in the schema. | ||
// | ||
// Defaults to only being exclusive with marks of the same type. You | ||
// can set it to an empty string (or any string not containing the | ||
// mark's own name) to allow multiple marks of a given type to | ||
// coexist (as long as they have different attributes). | ||
// | ||
// group:: ?string | ||
// The group or space-separated groups to which this node belongs. | ||
// | ||
// toDOM:: ?(mark: Mark) → DOMOutputSpec | ||
@@ -361,14 +401,20 @@ // Defines the default way marks of this type should be serialized | ||
constructor(spec) { | ||
// :: OrderedMap<NodeSpec> The node specs that the schema is based on. | ||
this.nodeSpec = OrderedMap.from(spec.nodes) | ||
// :: OrderedMap<MarkSpec> The mark spec that the schema is based on. | ||
this.markSpec = OrderedMap.from(spec.marks) | ||
// :: SchemaSpec | ||
// The [spec](#model.SchemaSpec) on which the schema is based, | ||
// with the added guarantee that its `nodes` and `marks` | ||
// properties are | ||
// [`OrderedMap`](https://github.com/marijnh/orderedmap) instances | ||
// (not raw objects or null). | ||
this.spec = {} | ||
for (let prop in spec) this.spec[prop] = spec[prop] | ||
this.spec.nodes = OrderedMap.from(spec.nodes) | ||
this.spec.marks = OrderedMap.from(spec.marks) | ||
// :: Object<NodeType> | ||
// An object mapping the schema's node names to node type objects. | ||
this.nodes = NodeType.compile(this.nodeSpec, this) | ||
this.nodes = NodeType.compile(this.spec.nodes, this) | ||
// :: Object<MarkType> | ||
// A map from mark names to mark type objects. | ||
this.marks = MarkType.compile(this.markSpec, this) | ||
this.marks = MarkType.compile(this.spec.marks, this) | ||
@@ -379,4 +425,8 @@ for (let prop in this.nodes) { | ||
let type = this.nodes[prop] | ||
type.contentExpr = ContentExpr.parse(type, this.nodeSpec.get(prop).content || "", this.nodeSpec) | ||
type.contentExpr = ContentExpr.parse(type, this.spec.nodes.get(prop).content || "", this.spec.nodes) | ||
} | ||
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(" ")) | ||
} | ||
@@ -392,4 +442,19 @@ // :: Object | ||
this.markFromJSON = this.markFromJSON.bind(this) | ||
// :: NodeType | ||
// The type of the [default top node](#model.SchemaSpec.topNode) | ||
// for this schema. | ||
this.topNodeType = this.nodes[this.spec.topNode || "doc"] | ||
} | ||
get nodeSpec() { | ||
warnAboutSpec() | ||
return this.spec.nodes | ||
} | ||
get markSpec() { | ||
warnAboutSpec() | ||
return this.spec.marks | ||
} | ||
// :: (union<string, NodeType>, ?Object, ?union<Fragment, Node, [Node]>, ?[Mark]) → Node | ||
@@ -447,1 +512,9 @@ // Create a node in this schema. The `type` may be a string or a | ||
exports.Schema = Schema | ||
let warnedAboutSpec = false | ||
function warnAboutSpec() { | ||
if (!warnedAboutSpec && typeof console != "undefined" && console.warn) { | ||
warnedAboutSpec = true | ||
console.warn("The Schema properties .nodeSpec and .markSpec are deprecated. Use .spec.nodes and .spec.marks instead") | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
236882
5621