prosemirror-model
Advanced tools
Comparing version 1.22.2 to 1.22.3
@@ -0,1 +1,7 @@ | ||
## 1.22.3 (2024-08-06) | ||
### Bug fixes | ||
Fix some corner cases in the way the DOM parser tracks active marks. | ||
## 1.22.2 (2024-07-18) | ||
@@ -2,0 +8,0 @@ |
@@ -1113,3 +1113,3 @@ import OrderedMap from 'orderedmap'; | ||
A function or type name used to validate values of this | ||
attibute. This will be used when deserializing the attribute | ||
attribute. This will be used when deserializing the attribute | ||
from JSON, and when running [`Node.check`](https://prosemirror.net/docs/ref/#model.Node.check). | ||
@@ -1116,0 +1116,0 @@ When a function, it should raise an exception if the value isn't |
{ | ||
"name": "prosemirror-model", | ||
"version": "1.22.2", | ||
"version": "1.22.3", | ||
"description": "ProseMirror's document model", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -219,3 +219,3 @@ import {Fragment} from "./fragment" | ||
let context = new ParseContext(this, options, false) | ||
context.addAll(dom, options.from, options.to) | ||
context.addAll(dom, Mark.none, options.from, options.to) | ||
return context.finish() as Node | ||
@@ -232,3 +232,3 @@ } | ||
let context = new ParseContext(this, options, true) | ||
context.addAll(dom, options.from, options.to) | ||
context.addAll(dom, Mark.none, options.from, options.to) | ||
return Slice.maxOpen(context.finish() as Fragment) | ||
@@ -344,4 +344,2 @@ } | ||
activeMarks: readonly Mark[] = Mark.none | ||
// Nested Marks with same type | ||
stashMarks: Mark[] = [] | ||
@@ -351,6 +349,3 @@ constructor( | ||
readonly attrs: Attrs | null, | ||
// Marks applied to this node itself | ||
readonly marks: readonly Mark[], | ||
// Marks that can't apply here, but will be used in children if possible | ||
public pendingMarks: readonly Mark[], | ||
readonly solid: boolean, | ||
@@ -397,18 +392,2 @@ match: ContentMatch | null, | ||
popFromStashMark(mark: Mark) { | ||
for (let i = this.stashMarks.length - 1; i >= 0; i--) | ||
if (mark.eq(this.stashMarks[i])) return this.stashMarks.splice(i, 1)[0] | ||
} | ||
applyPending(nextType: NodeType) { | ||
for (let i = 0, pending = this.pendingMarks; i < pending.length; i++) { | ||
let mark = pending[i] | ||
if ((this.type ? this.type.allowsMarkType(mark.type) : markMayApply(mark.type, nextType)) && | ||
!mark.isInSet(this.activeMarks)) { | ||
this.activeMarks = mark.addToSet(this.activeMarks) | ||
this.pendingMarks = mark.removeFromSet(this.pendingMarks) | ||
} | ||
} | ||
} | ||
inlineContext(node: DOMNode) { | ||
@@ -437,8 +416,8 @@ if (this.type) return this.type.inlineContent | ||
if (topNode) | ||
topContext = new NodeContext(topNode.type, topNode.attrs, Mark.none, Mark.none, true, | ||
topContext = new NodeContext(topNode.type, topNode.attrs, Mark.none, true, | ||
options.topMatch || topNode.type.contentMatch, topOptions) | ||
else if (isOpen) | ||
topContext = new NodeContext(null, null, Mark.none, Mark.none, true, null, topOptions) | ||
topContext = new NodeContext(null, null, Mark.none, true, null, topOptions) | ||
else | ||
topContext = new NodeContext(parser.schema.topNodeType, null, Mark.none, Mark.none, true, null, topOptions) | ||
topContext = new NodeContext(parser.schema.topNodeType, null, Mark.none, true, null, topOptions) | ||
this.nodes = [topContext] | ||
@@ -456,21 +435,8 @@ this.find = options.findPositions | ||
// `style` attribute, `addElementWithStyles`. | ||
addDOM(dom: DOMNode) { | ||
if (dom.nodeType == 3) this.addTextNode(dom as Text) | ||
else if (dom.nodeType == 1) this.addElement(dom as HTMLElement) | ||
addDOM(dom: DOMNode, marks: readonly Mark[]) { | ||
if (dom.nodeType == 3) this.addTextNode(dom as Text, marks) | ||
else if (dom.nodeType == 1) this.addElement(dom as HTMLElement, marks) | ||
} | ||
withStyleRules(dom: HTMLElement, f: () => void) { | ||
let style = dom.style | ||
if (!style || !style.length) return f() | ||
let marks = this.readStyles(dom.style) | ||
if (!marks) return // A style with ignore: true | ||
let [addMarks, removeMarks] = marks, top = this.top | ||
for (let i = 0; i < removeMarks.length; i++) this.removePendingMark(removeMarks[i], top) | ||
for (let i = 0; i < addMarks.length; i++) this.addPendingMark(addMarks[i]) | ||
f() | ||
for (let i = 0; i < addMarks.length; i++) this.removePendingMark(addMarks[i], top) | ||
for (let i = 0; i < removeMarks.length; i++) this.addPendingMark(removeMarks[i]) | ||
} | ||
addTextNode(dom: Text) { | ||
addTextNode(dom: Text, marks: readonly Mark[]) { | ||
let value = dom.nodeValue! | ||
@@ -499,3 +465,3 @@ let top = this.top | ||
} | ||
if (value) this.insertNode(this.parser.schema.text(value)) | ||
if (value) this.insertNode(this.parser.schema.text(value), marks) | ||
this.findInText(dom) | ||
@@ -509,3 +475,3 @@ } else { | ||
// none is found, the element's content nodes are added directly. | ||
addElement(dom: HTMLElement, matchAfter?: TagParseRule) { | ||
addElement(dom: HTMLElement, marks: readonly Mark[], matchAfter?: TagParseRule) { | ||
let name = dom.nodeName.toLowerCase(), ruleID: TagParseRule | undefined | ||
@@ -517,3 +483,3 @@ if (listTags.hasOwnProperty(name) && this.parser.normalizeLists) normalizeList(dom) | ||
this.findInside(dom) | ||
this.ignoreFallback(dom) | ||
this.ignoreFallback(dom, marks) | ||
} else if (!rule || rule.skip || rule.closeParent) { | ||
@@ -531,13 +497,13 @@ if (rule && rule.closeParent) this.open = Math.max(0, this.open - 1) | ||
} else if (!dom.firstChild) { | ||
this.leafFallback(dom) | ||
this.leafFallback(dom, marks) | ||
return | ||
} | ||
if (rule && rule.skip) this.addAll(dom) | ||
else this.withStyleRules(dom, () => this.addAll(dom)) | ||
let innerMarks = rule && rule.skip ? marks : this.readStyles(dom, marks) | ||
if (innerMarks) this.addAll(dom, innerMarks) | ||
if (sync) this.sync(top) | ||
this.needsBlock = oldNeedsBlock | ||
} else { | ||
this.withStyleRules(dom, () => { | ||
this.addElementByRule(dom, rule as TagParseRule, rule!.consuming === false ? ruleID : undefined) | ||
}) | ||
let innerMarks = this.readStyles(dom, marks) | ||
if (innerMarks) | ||
this.addElementByRule(dom, rule as TagParseRule, innerMarks, rule!.consuming === false ? ruleID : undefined) | ||
} | ||
@@ -547,19 +513,19 @@ } | ||
// Called for leaf DOM nodes that would otherwise be ignored | ||
leafFallback(dom: DOMNode) { | ||
leafFallback(dom: DOMNode, marks: readonly Mark[]) { | ||
if (dom.nodeName == "BR" && this.top.type && this.top.type.inlineContent) | ||
this.addTextNode(dom.ownerDocument!.createTextNode("\n")) | ||
this.addTextNode(dom.ownerDocument!.createTextNode("\n"), marks) | ||
} | ||
// Called for ignored nodes | ||
ignoreFallback(dom: DOMNode) { | ||
ignoreFallback(dom: DOMNode, marks: readonly Mark[]) { | ||
// Ignored BR nodes should at least create an inline context | ||
if (dom.nodeName == "BR" && (!this.top.type || !this.top.type.inlineContent)) | ||
this.findPlace(this.parser.schema.text("-")) | ||
this.findPlace(this.parser.schema.text("-"), marks) | ||
} | ||
// Run any style parser associated with the node's styles. Either | ||
// return an array of marks, or null to indicate some of the styles | ||
// had a rule with `ignore` set. | ||
readStyles(styles: CSSStyleDeclaration) { | ||
let add = Mark.none, remove = Mark.none | ||
// return an updated array of marks, or null to indicate some of the | ||
// styles had a rule with `ignore` set. | ||
readStyles(dom: HTMLElement, marks: readonly Mark[]) { | ||
let styles = dom.style | ||
// Because many properties will only show up in 'normalized' form | ||
@@ -570,3 +536,3 @@ // in `style.item` (i.e. text-decoration becomes | ||
// over the items. | ||
if (styles.length) for (let i = 0; i < this.parser.matchedStyles.length; i++) { | ||
if (styles && styles.length) for (let i = 0; i < this.parser.matchedStyles.length; i++) { | ||
let name = this.parser.matchedStyles[i], value = styles.getPropertyValue(name) | ||
@@ -577,9 +543,6 @@ if (value) for (let after: StyleParseRule | undefined = undefined;;) { | ||
if (rule.ignore) return null | ||
if (rule.clearMark) { | ||
this.top.pendingMarks.concat(this.top.activeMarks).forEach(m => { | ||
if (rule!.clearMark!(m)) remove = m.addToSet(remove) | ||
}) | ||
} else { | ||
add = this.parser.schema.marks[rule.mark!].create(rule.attrs).addToSet(add) | ||
} | ||
if (rule.clearMark) | ||
marks = marks.filter(m => !rule!.clearMark!(m)) | ||
else | ||
marks = marks.concat(this.parser.schema.marks[rule.mark!].create(rule.attrs)) | ||
if (rule.consuming === false) after = rule | ||
@@ -589,3 +552,3 @@ else break | ||
} | ||
return [add, remove] | ||
return marks | ||
} | ||
@@ -596,15 +559,18 @@ | ||
// the node's content is wrapped, and return true. | ||
addElementByRule(dom: HTMLElement, rule: TagParseRule, continueAfter?: TagParseRule) { | ||
let sync, nodeType, mark | ||
addElementByRule(dom: HTMLElement, rule: TagParseRule, marks: readonly Mark[], continueAfter?: TagParseRule) { | ||
let sync, nodeType | ||
if (rule.node) { | ||
nodeType = this.parser.schema.nodes[rule.node] | ||
if (!nodeType.isLeaf) { | ||
sync = this.enter(nodeType, rule.attrs || null, rule.preserveWhitespace) | ||
} else if (!this.insertNode(nodeType.create(rule.attrs))) { | ||
this.leafFallback(dom) | ||
let inner = this.enter(nodeType, rule.attrs || null, marks, rule.preserveWhitespace) | ||
if (inner) { | ||
sync = true | ||
marks = inner | ||
} | ||
} else if (!this.insertNode(nodeType.create(rule.attrs), marks)) { | ||
this.leafFallback(dom, marks) | ||
} | ||
} else { | ||
let markType = this.parser.schema.marks[rule.mark!] | ||
mark = markType.create(rule.attrs) | ||
this.addPendingMark(mark) | ||
marks = marks.concat(markType.create(rule.attrs)) | ||
} | ||
@@ -616,6 +582,6 @@ let startIn = this.top | ||
} else if (continueAfter) { | ||
this.addElement(dom, continueAfter) | ||
this.addElement(dom, marks, continueAfter) | ||
} else if (rule.getContent) { | ||
this.findInside(dom) | ||
rule.getContent(dom, this.parser.schema).forEach(node => this.insertNode(node)) | ||
rule.getContent(dom, this.parser.schema).forEach(node => this.insertNode(node, marks)) | ||
} else { | ||
@@ -627,6 +593,5 @@ let contentDOM = dom | ||
this.findAround(dom, contentDOM, true) | ||
this.addAll(contentDOM) | ||
this.addAll(contentDOM, marks) | ||
} | ||
if (sync && this.sync(startIn)) this.open-- | ||
if (mark) this.removePendingMark(mark, startIn) | ||
} | ||
@@ -637,3 +602,3 @@ | ||
// synchronize after every block element. | ||
addAll(parent: DOMNode, startIndex?: number, endIndex?: number) { | ||
addAll(parent: DOMNode, marks: readonly Mark[], startIndex?: number, endIndex?: number) { | ||
let index = startIndex || 0 | ||
@@ -644,3 +609,3 @@ for (let dom = startIndex ? parent.childNodes[startIndex] : parent.firstChild, | ||
this.findAtPoint(parent, index) | ||
this.addDOM(dom!) | ||
this.addDOM(dom!, marks) | ||
} | ||
@@ -653,3 +618,3 @@ this.findAtPoint(parent, index) | ||
// nodes that we're in. | ||
findPlace(node: Node) { | ||
findPlace(node: Node, marks: readonly Mark[]) { | ||
let route, sync: NodeContext | undefined | ||
@@ -666,25 +631,25 @@ for (let depth = this.open; depth >= 0; depth--) { | ||
} | ||
if (!route) return false | ||
if (!route) return null | ||
this.sync(sync!) | ||
for (let i = 0; i < route.length; i++) | ||
this.enterInner(route[i], null, false) | ||
return true | ||
marks = this.enterInner(route[i], null, marks, false) | ||
return marks | ||
} | ||
// Try to insert the given node, adjusting the context when needed. | ||
insertNode(node: Node) { | ||
insertNode(node: Node, marks: readonly Mark[]) { | ||
if (node.isInline && this.needsBlock && !this.top.type) { | ||
let block = this.textblockFromContext() | ||
if (block) this.enterInner(block) | ||
if (block) marks = this.enterInner(block, null, marks) | ||
} | ||
if (this.findPlace(node)) { | ||
let innerMarks = this.findPlace(node, marks) | ||
if (innerMarks) { | ||
this.closeExtra() | ||
let top = this.top | ||
top.applyPending(node.type) | ||
if (top.match) top.match = top.match.matchType(node.type) | ||
let marks = top.activeMarks | ||
for (let i = 0; i < node.marks.length; i++) | ||
if (!top.type || top.type.allowsMarkType(node.marks[i].type)) | ||
marks = node.marks[i].addToSet(marks) | ||
top.content.push(node.mark(marks)) | ||
let nodeMarks = Mark.none | ||
for (let m of innerMarks.concat(node.marks)) | ||
if (top.type ? top.type.allowsMarkType(m.type) : markMayApply(m.type, node.type)) | ||
nodeMarks = m.addToSet(nodeMarks) | ||
top.content.push(node.mark(nodeMarks)) | ||
return true | ||
@@ -697,18 +662,27 @@ } | ||
// necessary. | ||
enter(type: NodeType, attrs: Attrs | null, preserveWS?: boolean | "full") { | ||
let ok = this.findPlace(type.create(attrs)) | ||
if (ok) this.enterInner(type, attrs, true, preserveWS) | ||
return ok | ||
enter(type: NodeType, attrs: Attrs | null, marks: readonly Mark[], preserveWS?: boolean | "full") { | ||
let innerMarks = this.findPlace(type.create(attrs), marks) | ||
if (innerMarks) innerMarks = this.enterInner(type, attrs, marks, true, preserveWS) | ||
return innerMarks | ||
} | ||
// Open a node of the given type | ||
enterInner(type: NodeType, attrs: Attrs | null = null, solid: boolean = false, preserveWS?: boolean | "full") { | ||
enterInner(type: NodeType, attrs: Attrs | null, marks: readonly Mark[], | ||
solid: boolean = false, preserveWS?: boolean | "full") { | ||
this.closeExtra() | ||
let top = this.top | ||
top.applyPending(type) | ||
top.match = top.match && top.match.matchType(type) | ||
let options = wsOptionsFor(type, preserveWS, top.options) | ||
if ((top.options & OPT_OPEN_LEFT) && top.content.length == 0) options |= OPT_OPEN_LEFT | ||
this.nodes.push(new NodeContext(type, attrs, top.activeMarks, top.pendingMarks, solid, null, options)) | ||
let applyMarks = Mark.none | ||
marks = marks.filter(m => { | ||
if (top.type ? top.type.allowsMarkType(m.type) : markMayApply(m.type, type)) { | ||
applyMarks = m.addToSet(applyMarks) | ||
return false | ||
} | ||
return true | ||
}) | ||
this.nodes.push(new NodeContext(type, attrs, applyMarks, solid, null, options)) | ||
this.open++ | ||
return marks | ||
} | ||
@@ -825,24 +799,2 @@ | ||
} | ||
addPendingMark(mark: Mark) { | ||
let found = findSameMarkInSet(mark, this.top.pendingMarks) | ||
if (found) this.top.stashMarks.push(found) | ||
this.top.pendingMarks = mark.addToSet(this.top.pendingMarks) | ||
} | ||
removePendingMark(mark: Mark, upto: NodeContext) { | ||
for (let depth = this.open; depth >= 0; depth--) { | ||
let level = this.nodes[depth] | ||
let found = level.pendingMarks.lastIndexOf(mark) | ||
if (found > -1) { | ||
level.pendingMarks = mark.removeFromSet(level.pendingMarks) | ||
} else { | ||
level.activeMarks = mark.removeFromSet(level.activeMarks) | ||
let stashMark = level.popFromStashMark(mark) | ||
if (stashMark && level.type && level.type.allowsMarkType(stashMark.type)) | ||
level.activeMarks = stashMark.addToSet(level.activeMarks) | ||
} | ||
if (level == upto) break | ||
} | ||
} | ||
} | ||
@@ -897,7 +849,1 @@ | ||
} | ||
function findSameMarkInSet(mark: Mark, set: readonly Mark[]) { | ||
for (let i = 0; i < set.length; i++) { | ||
if (mark.eq(set[i])) return set[i] | ||
} | ||
} |
@@ -545,3 +545,3 @@ import OrderedMap from "orderedmap" | ||
/// A function or type name used to validate values of this | ||
/// attibute. This will be used when deserializing the attribute | ||
/// attribute. This will be used when deserializing the attribute | ||
/// from JSON, and when running [`Node.check`](#model.Node.check). | ||
@@ -548,0 +548,0 @@ /// When a function, it should raise an exception if the value isn't |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
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
519428
11087