prosemirror-view
Advanced tools
Comparing version 0.11.2 to 0.12.0
@@ -26,3 +26,3 @@ var ref = require("prosemirror-state"); | ||
function selectNodeHorizontally(view, dir) { | ||
function selectHorizontally(view, dir) { | ||
var ref = view.state.selection; | ||
@@ -46,3 +46,4 @@ var empty = ref.empty; | ||
if (isSelectable(nextNode) && offset == $from.parentOffset - (dir > 0 ? 0 : nextNode.nodeSize)) | ||
{ return apply(view, new NodeSelection(dir < 0 ? view.state.doc.resolve($from.pos - nextNode.nodeSize) : $from)) } | ||
{ return apply(view, new NodeSelection(dir < 0 ? view.state.doc.resolve($from.pos - nextNode.nodeSize) : $from)) | ||
; }(dir < 0 ? skipIgnoredNodesLeft : skipIgnoredNodesRight)(view) | ||
return null | ||
@@ -59,2 +60,69 @@ } | ||
function nodeLen(node) { | ||
return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length | ||
} | ||
// Make sure the cursor isn't directly after one or more ignored | ||
// nodes, which will confuse the browser's cursor motion logic. | ||
function skipIgnoredNodesLeft(view) { | ||
var sel = view.root.getSelection(), moved = false | ||
var node = sel.anchorNode, offset = sel.anchorOffset | ||
for (;;) { | ||
if (offset > 0) { | ||
if (node.nodeType != 1) { break } | ||
var before = node.childNodes[offset - 1] | ||
if (before.nodeType == 1 && before.hasAttribute("pm-ignore")) { moved = true; offset-- } | ||
else { break } | ||
} else { | ||
var prev = node.previousSibling | ||
while (prev && prev.nodeType == 1 && prev.hasAttribute("pm-ignore")) { moved = prev = prev.previousSibling } | ||
if (!prev) { | ||
node = node.parentNode | ||
if (node == view.content) { break } | ||
offset = 0 | ||
} else { | ||
node = prev | ||
offset = nodeLen(node) | ||
} | ||
} | ||
} | ||
if (moved) { setSel(sel, node, offset) } | ||
} | ||
// Make sure the cursor isn't directly before one or more ignored | ||
// nodes. | ||
function skipIgnoredNodesRight(view) { | ||
var sel = view.root.getSelection(), moved = false | ||
var node = sel.anchorNode, offset = sel.anchorOffset, len = nodeLen(node) | ||
for (;;) { | ||
if (offset < len) { | ||
if (node.nodeType != 1) { break } | ||
var after = node.childNodes[offset] | ||
if (after.nodeType == 1 && after.hasAttribute("pm-ignore")) { moved = true; offset++ } | ||
else { break } | ||
} else { | ||
var next = node.nextSibling | ||
while (next && next.nodeType == 1 && next.hasAttribute("pm-ignore")) { moved = next = next.previousSibling } | ||
if (!next) { | ||
node = node.parentNode | ||
if (node == view.content) { break } | ||
offset = len = 0 | ||
} else { | ||
node = next | ||
offset = 0 | ||
len = nodeLen(node) | ||
} | ||
} | ||
} | ||
if (moved) { setSel(sel, node, offset) } | ||
} | ||
function setSel(sel, node, offset) { | ||
var range = document.createRange() | ||
range.setEnd(node, offset) | ||
range.setStart(node, offset) | ||
sel.removeAllRanges() | ||
sel.addRange(range) | ||
} | ||
// : (EditorState, number) | ||
@@ -64,3 +132,3 @@ // Check whether vertical selection motion would involve node | ||
// browser) | ||
function selectNodeVertically(view, dir) { | ||
function selectVertically(view, dir) { | ||
var ref = view.state.selection; | ||
@@ -108,11 +176,11 @@ var empty = ref.empty; | ||
} else if (code == 37) { // Left arrow | ||
return selectNodeHorizontally(view, -1) | ||
return selectHorizontally(view, -1) | ||
} else if (code == 39) { // Right arrow | ||
return selectNodeHorizontally(view, 1) | ||
return selectHorizontally(view, 1) | ||
} else if (code == 38) { // Up arrow | ||
return selectNodeVertically(view, -1) | ||
return selectVertically(view, -1) | ||
} else if (code == 40) { // Down arrow | ||
return selectNodeVertically(view, 1) | ||
return selectVertically(view, 1) | ||
} | ||
} | ||
exports.captureKeyDown = captureKeyDown |
@@ -144,3 +144,3 @@ var ref = require("prosemirror-model"); | ||
// Mark nodes touched by this change as 'to be redrawn' | ||
markDirtyFor(view, doc, change.start, change.endA) | ||
view.dirty = {from: change.start, to: change.endA} | ||
@@ -217,9 +217,1 @@ var $from = parsed.resolveNoCache(change.start - range.from) | ||
} | ||
function markDirtyFor(view, doc, start, end) { | ||
var $start = doc.resolve(start), $end = doc.resolve(end), same = $start.sameDepth($end) | ||
if (same == 0) | ||
{ view.markAllDirty() } | ||
else | ||
{ view.markRangeDirty($start.before(same), $start.after(same), doc) } | ||
} |
function isEditorContent(dom) { | ||
return dom.classList.contains("ProseMirror-content") | ||
return dom.nodeType == 1 && dom.classList.contains("ProseMirror-content") | ||
} | ||
// : (dom.Node) → number | ||
// Get the position before a given a DOM node in a document. | ||
// Get the position before a given a DOM node in a document. Returns | ||
// -1 when the first annotated parent node is the top-level document. | ||
function posBeforeFromDOM(node) { | ||
var pos = 0, add = 0 | ||
var pos = -1 | ||
for (var cur = node; !isEditorContent(cur); cur = cur.parentNode) { | ||
var attr = cur.getAttribute("pm-offset") | ||
if (attr) { pos += +attr + add; add = 1 } | ||
var attr = cur.nodeType == 1 && cur.getAttribute("pm-offset") | ||
if (attr) { pos += +attr + 1 } | ||
} | ||
@@ -16,5 +17,3 @@ return pos | ||
var posFromDOMResult = {pos: 0, inLeaf: -1} | ||
// : (dom.Node, number) → {pos: number, inLeaf: number} | ||
// : (dom.Node, number) → number | ||
function posFromDOM(dom, domOffset, bias) { | ||
@@ -35,2 +34,4 @@ if ( bias === void 0 ) bias = 0; | ||
innerOffset += domOffset | ||
} else if (dom.nodeType != 1 || dom.hasAttribute("pm-ignore")) { | ||
innerOffset = 0 | ||
} else if (tag = dom.getAttribute("pm-offset") && !childContainer(dom)) { | ||
@@ -41,5 +42,3 @@ var size = +dom.getAttribute("pm-size") | ||
else { innerOffset = Math.min(innerOffset, size) } | ||
var inLeaf = posFromDOMResult.inLeaf = posBeforeFromDOM(dom) | ||
posFromDOMResult.pos = inLeaf + innerOffset | ||
return posFromDOMResult | ||
return posBeforeFromDOM(dom) + innerOffset | ||
} else if (dom.hasAttribute("pm-container")) { | ||
@@ -58,3 +57,3 @@ break | ||
var start = isEditorContent(dom) ? 0 : posBeforeFromDOM(dom) + 1, before = 0 | ||
var start = posBeforeFromDOM(dom) + 1, before = 0 | ||
@@ -68,5 +67,3 @@ for (var child = dom.childNodes[domOffset - 1]; child; child = child.previousSibling) { | ||
posFromDOMResult.inLeaf = -1 | ||
posFromDOMResult.pos = start + before + innerOffset | ||
return posFromDOMResult | ||
return start + before + innerOffset | ||
} | ||
@@ -77,3 +74,3 @@ exports.posFromDOM = posFromDOM | ||
function childContainer(dom) { | ||
return dom.hasAttribute("pm-container") ? dom : dom.querySelector("[pm-container]") | ||
return dom.nodeType != 1 ? null : dom.hasAttribute("pm-container") ? dom : dom.querySelector("[pm-container]") | ||
} | ||
@@ -278,3 +275,3 @@ exports.childContainer = childContainer | ||
} | ||
return posFromDOM(node, offset, bias) | ||
return {pos: posFromDOM(node, offset, bias), inside: posBeforeFromDOM(node)} | ||
} | ||
@@ -281,0 +278,0 @@ exports.posAtCoords = posAtCoords |
297
dist/draw.js
@@ -8,115 +8,26 @@ var ref = require("prosemirror-model"); | ||
var DIRTY_RESCAN = 1, DIRTY_REDRAW = 2 | ||
exports.DIRTY_RESCAN = DIRTY_RESCAN; exports.DIRTY_REDRAW = DIRTY_REDRAW | ||
// FIXME track dirty ranges in a better way | ||
function setup(view) { | ||
var serializer = view.someProp("domSerializer") || DOMSerializer.fromSchema(view.state.schema) | ||
var options = { | ||
pos: 0, | ||
onRender: function onRender(node, dom, _pos, offset) { | ||
if (node.isBlock) { | ||
if (offset != null) | ||
{ dom.setAttribute("pm-offset", offset) } | ||
dom.setAttribute("pm-size", node.nodeSize) | ||
if (node.isTextblock) | ||
{ adjustTrailingHacks(serializer, dom, node) } | ||
if (dom.contentEditable == "false") { | ||
var wrap = document.createElement("div") | ||
wrap.appendChild(dom) | ||
dom = wrap | ||
} | ||
} | ||
return dom | ||
}, | ||
onContainer: function onContainer(dom) { | ||
dom.setAttribute("pm-container", true) | ||
}, | ||
// : (Node, dom.Node, number, number) → dom.Node | ||
renderInlineFlat: function renderInlineFlat(node, dom, _pos, offset) { | ||
var inner = dom | ||
for (var i = 0; i < node.marks.length; i++) { inner = inner.firstChild } | ||
if (dom.nodeType != 1) { | ||
var wrap = document.createElement("span") | ||
wrap.appendChild(dom) | ||
dom = wrap | ||
} | ||
dom.setAttribute("pm-offset", offset) | ||
dom.setAttribute("pm-size", node.nodeSize) | ||
return dom | ||
}, | ||
document: document | ||
} | ||
return {serializer: serializer, options: options} | ||
function getSerializer(view) { | ||
return view.someProp("domSerializer") || DOMSerializer.fromSchema(view.state.schema) | ||
} | ||
function draw(view, doc) { | ||
function draw(view, doc, decorations) { | ||
view.content.textContent = "" | ||
var ref = setup(view); | ||
var options = ref.options; | ||
var serializer = ref.serializer; | ||
view.content.appendChild(serializer.serializeFragment(doc.content, options)) | ||
new Context(getSerializer(view), decorations).serializeContent(doc, view.content) | ||
} | ||
exports.draw = draw | ||
function isBR(node, serializer) { | ||
if (!node.isLeaf || node.isText || !node.isInline) { return false } | ||
var ser = serializer.nodes[node.type.name](node) | ||
return Array.isArray(ser) ? ser[0] == "br" : ser && ser.nodeName == "BR" | ||
} | ||
function adjustTrailingHacks(serializer, dom, node) { | ||
var needs = node.content.size == 0 || isBR(node.lastChild, serializer) || | ||
(node.type.spec.code && node.lastChild.isText && /\n$/.test(node.lastChild.text)) | ||
? "br" : !node.lastChild.isText && node.lastChild.isLeaf ? "text" : null | ||
var last = dom.lastChild | ||
var has = !last || last.nodeType != 1 || !last.hasAttribute("pm-ignore") ? null | ||
: last.nodeName == "BR" ? "br" : "text" | ||
if (needs != has) { | ||
if (has) { dom.removeChild(last) } | ||
if (needs) { | ||
var add = document.createElement(needs == "br" ? "br" : "span") | ||
add.setAttribute("pm-ignore", needs == "br" ? "trailing-break" : "cursor-text") | ||
dom.appendChild(add) | ||
} | ||
} | ||
} | ||
function findNodeIn(parent, i, node) { | ||
for (; i < parent.childCount; i++) { | ||
var child = parent.child(i) | ||
if (child == node) { return i } | ||
} | ||
return -1 | ||
} | ||
function movePast(dom, view, onUnmount) { | ||
var next = dom.nextSibling | ||
for (var i = 0; i < onUnmount.length; i++) { onUnmount[i](view, dom) } | ||
dom.parentNode.removeChild(dom) | ||
return next | ||
} | ||
function redraw(view, oldState, newState) { | ||
var dirty = view.dirtyNodes | ||
if (dirty.get(oldState.doc) == DIRTY_REDRAW) { return draw(view, newState.doc) } | ||
var ref = setup(view); | ||
var serializer = ref.serializer; | ||
var opts = ref.options; | ||
function redraw(view, oldDoc, newDoc, oldDecorations, newDecorations) { | ||
var serializer = getSerializer(view) | ||
var onUnmountDOM = [] | ||
view.someProp("onUnmountDOM", function (f) { onUnmountDOM.push(f) }) | ||
function scan(dom, node, prev, pos) { | ||
function scan(dom, node, prev, oldDecorations, newDecorations) { | ||
var iPrev = 0, oPrev = 0, pChild = prev.firstChild | ||
var domPos = dom.firstChild | ||
while (domPos && (domPos.nodeType != 1 || domPos.hasAttribute("pm-ignore"))) | ||
{ domPos = movePast(domPos, view, onUnmountDOM) } | ||
var localDecorations = newDecorations.locals(node) | ||
var decoIndex = applyDecorations(localDecorations, 0, 0, 0, dom, domPos, false) | ||
function syncDOM() { | ||
@@ -132,5 +43,12 @@ while (domPos) { | ||
} | ||
var oldLocalDecorations, offset = 0, child | ||
function sameLocalDeco() { | ||
return compareDecorations(oldLocalDecorations || (oldLocalDecorations = oldDecorations.locals(prev)), | ||
localDecorations, decoIndex, | ||
oPrev, oPrev + pChild.nodeSize, offset, offset + child.nodeSize) | ||
} | ||
for (var iNode = 0, offset = 0; iNode < node.childCount; iNode++) { | ||
var child = node.child(iNode), matching = void 0, reuseDOM = void 0 | ||
for (var iNode = 0; iNode < node.childCount; iNode++) { | ||
var matching = void 0, reuseDOM = void 0 | ||
child = node.child(iNode) | ||
var found = pChild == child ? iPrev : findNodeIn(prev, iPrev + 1, child) | ||
@@ -145,15 +63,21 @@ if (found > -1) { | ||
if (matching && !dirty.get(matching) && syncDOM()) { | ||
var childDeco = newDecorations.forChild(offset, child), prevChildDeco = void 0, matchedLocalDeco = void 0 | ||
if (matching && | ||
childDeco.sameOutput(prevChildDeco = oldDecorations.forChild(offset, child)) && | ||
(matchedLocalDeco = sameLocalDeco()) != null && | ||
syncDOM()) { | ||
reuseDOM = true | ||
} else if (pChild && !child.isText && child.sameMarkup(pChild) && dirty.get(pChild) != DIRTY_REDRAW && syncDOM()) { | ||
decoIndex = matchedLocalDeco | ||
} else if (pChild && !child.isText && child.sameMarkup(pChild) && | ||
(matchedLocalDeco = sameLocalDeco()) != null && syncDOM()) { | ||
reuseDOM = true | ||
decoIndex = matchedLocalDeco | ||
if (!pChild.isLeaf) | ||
{ scan(childContainer(domPos), child, pChild, pos + offset + 1) } | ||
{ scan(childContainer(domPos), child, pChild, prevChildDeco || oldDecorations.forChild(oPrev, pChild), childDeco) } | ||
domPos.setAttribute("pm-size", child.nodeSize) | ||
} else { | ||
opts.pos = pos + offset | ||
opts.offset = offset | ||
var rendered = serializer.serializeNode(child, opts) | ||
var rendered = new Context(serializer, childDeco).serialize(child, offset) | ||
dom.insertBefore(rendered, domPos) | ||
reuseDOM = false | ||
decoIndex = applyDecorations(localDecorations, decoIndex, offset, offset + child.nodeSize, dom, rendered) | ||
} | ||
@@ -177,2 +101,4 @@ | ||
pChild = prev.maybeChild(++iPrev) | ||
var end$1 = offset + child.nodeSize | ||
decoIndex = applyDecorations(localDecorations, decoIndex, end$1, end$1, dom, domPos) | ||
} | ||
@@ -188,6 +114,157 @@ offset += child.nodeSize | ||
} | ||
scan(view.content, newState.doc, oldState.doc, 0) | ||
scan(view.content, newDoc, oldDoc, oldDecorations, newDecorations) | ||
} | ||
exports.redraw = redraw | ||
var Context = function Context(serializer, decorations) { | ||
this.serializer = serializer | ||
this.decorations = decorations | ||
}; | ||
Context.prototype.onContent = function onContent (parent, target) { | ||
target.setAttribute("pm-container", true) | ||
this.serializeContent(parent, target, this.decorations) | ||
}; | ||
Context.prototype.serialize = function serialize (node, offset) { | ||
var dom = this.serializer.serializeNodeAndMarks(node, this) | ||
if (dom.nodeType != 1 || dom.contentEditable == "false") { | ||
var wrap = document.createElement(node.isInline ? "span" : "div") | ||
wrap.appendChild(dom) | ||
dom = wrap | ||
} | ||
dom.setAttribute("pm-size", node.nodeSize) | ||
dom.setAttribute("pm-offset", offset) | ||
if (node.isTextblock) { adjustTrailingHacks(this.serializer, dom, node) } | ||
return dom | ||
}; | ||
Context.prototype.serializeContent = function serializeContent (node, target) { | ||
var this$1 = this; | ||
var decorations = this.decorations | ||
var locals = decorations.locals(node) | ||
var i = applyDecorations(locals, 0, 0, 0, target, null, false) | ||
node.content.forEach(function (child, offset) { | ||
this$1.decorations = decorations.forChild(offset, child) | ||
var dom = target.appendChild(this$1.serialize(child, offset)) | ||
i = applyDecorations(locals, i, offset, offset + child.nodeSize, target, dom) | ||
}) | ||
}; | ||
// : ([Decoration], number, number, number, dom.Node, ?dom.Node) → number | ||
// Used to apply decorations, either at a given point in a node that's | ||
// being updated, or those in and after a given child node. `i` is an | ||
// index into the local set of (non-overlapping) decorations, which is | ||
// used to avoid scanning through the array multiple times. | ||
// | ||
// When `from` == `to`, this should only draw inserted decorations at | ||
// the given position. When `from` < `to`, this should also decorate a | ||
// node. That node may be a text node, which may have different | ||
// decorations at different points, in which case it has to be split. | ||
// | ||
// `domNode` should be _the node after `from`_. That means that it is | ||
// the current node when `from` < `to`, and the node after the current | ||
// position when they are equal. It may be null, when `from` == `to` | ||
// and there are no nodes after the current point. | ||
// | ||
// Returns the updated index, which can be passed back to this | ||
// function later. | ||
function applyDecorations(locals, i, from, to, domParent, domNode) { | ||
var result = i | ||
for (; i < locals.length; i++) { | ||
var span = locals[i] | ||
if (span.from > to || (span.from == to && span.to > to)) { break } | ||
if (from < span.from) { | ||
domNode = span.from < to ? splitText(domNode, span.from - from) : domNode.nextSibling | ||
from = span.from | ||
} | ||
var curNode = domNode | ||
if (span.to < to && span.from < span.to) { | ||
domNode = splitText(domNode, span.to - from) | ||
from = span.to | ||
} | ||
for (;;) { | ||
curNode = span.type.apply(domParent, curNode) | ||
if (i < locals.length - 1 && locals[i + 1].to == span.to && locals[i + 1].from == span.from) { span = locals[++i] } | ||
else { break } | ||
} | ||
if (span.to <= to) { result = i + 1 } | ||
} | ||
return result | ||
} | ||
function compareDecorations(old, cur, i, oldFrom, oldTo, curFrom, curTo) { | ||
var j = 0, result = i | ||
while (j < old.length && old[j].to <= oldFrom) { j++ } | ||
for (;; i++, j++) { | ||
var oldEnd = j == old.length || old[j].from >= oldTo | ||
if (i == cur.length || cur[i].from >= curTo) { return oldEnd ? result : null } | ||
else if (oldEnd) { return null } | ||
var oldNext = old[j], curNext = cur[i] | ||
if (oldNext.type != curNext.type || | ||
oldNext.from - oldFrom != curNext.from - curFrom || | ||
oldNext.to - oldFrom != curNext.to - curFrom) { return null } | ||
if (curNext.to <= curTo) { result = i + 1 } | ||
} | ||
} | ||
function splitText(node, offset) { | ||
var inner = node | ||
while (inner.nodeType != 3) { inner = inner.firstChild } | ||
var newNode = document.createTextNode(inner.nodeValue.slice(offset)) | ||
inner.nodeValue = inner.nodeValue.slice(0, offset) | ||
while (inner != node) { | ||
var parent = inner.parentNode, wrap = parent.cloneNode(false) | ||
wrap.appendChild(newNode) | ||
newNode = wrap | ||
inner = parent | ||
} | ||
node.parentNode.insertBefore(newNode, node.nextSibling) | ||
var size = +node.getAttribute("pm-size") | ||
newNode.setAttribute("pm-size", size - offset) | ||
node.setAttribute("pm-size", offset) | ||
newNode.setAttribute("pm-offset", +node.getAttribute("pm-offset") + offset) | ||
return newNode | ||
} | ||
function findNodeIn(parent, i, node) { | ||
for (; i < parent.childCount; i++) { | ||
var child = parent.child(i) | ||
if (child == node) { return i } | ||
} | ||
return -1 | ||
} | ||
function movePast(dom, view, onUnmount) { | ||
var next = dom.nextSibling | ||
for (var i = 0; i < onUnmount.length; i++) { onUnmount[i](view, dom) } | ||
dom.parentNode.removeChild(dom) | ||
return next | ||
} | ||
function isBR(node, serializer) { | ||
if (!node.isLeaf || node.isText || !node.isInline) { return false } | ||
var ser = serializer.nodes[node.type.name](node) | ||
return Array.isArray(ser) ? ser[0] == "br" : ser && ser.nodeName == "BR" | ||
} | ||
function adjustTrailingHacks(serializer, dom, node) { | ||
var needs = node.content.size == 0 || isBR(node.lastChild, serializer) || | ||
(node.type.spec.code && node.lastChild.isText && /\n$/.test(node.lastChild.text)) | ||
? "br" : !node.lastChild.isText && node.lastChild.isLeaf ? "text" : null | ||
var last = dom.lastChild | ||
var has = !last || last.nodeType != 1 || !last.hasAttribute("pm-ignore") ? null | ||
: last.nodeName == "BR" ? "br" : "text" | ||
if (needs != has) { | ||
if (has == "br") { dom.removeChild(last) } | ||
if (needs) { | ||
var add = document.createElement(needs == "br" ? "br" : "span") | ||
add.setAttribute("pm-ignore", needs == "br" ? "trailing-break" : "cursor-text") | ||
dom.appendChild(add) | ||
} | ||
} | ||
} | ||
function iosHacks(dom) { | ||
@@ -194,0 +271,0 @@ if (dom.nodeName == "UL" || dom.nodeName == "OL") { |
@@ -1,20 +0,20 @@ | ||
var ref = require("./map"); | ||
var Map = ref.Map; | ||
var ref$1 = require("./dompos"); | ||
var scrollPosIntoView = ref$1.scrollPosIntoView; | ||
var posAtCoords = ref$1.posAtCoords; | ||
var coordsAtPos = ref$1.coordsAtPos; | ||
var ref$2 = require("./draw"); | ||
var draw = ref$2.draw; | ||
var redraw = ref$2.redraw; | ||
var DIRTY_REDRAW = ref$2.DIRTY_REDRAW; | ||
var DIRTY_RESCAN = ref$2.DIRTY_RESCAN; | ||
var ref$3 = require("./input"); | ||
var initInput = ref$3.initInput; | ||
var finishUpdateFromDOM = ref$3.finishUpdateFromDOM; | ||
var dispatchKeyDown = ref$3.dispatchKeyDown; | ||
var dispatchKeyPress = ref$3.dispatchKeyPress; | ||
var ref$4 = require("./selection"); | ||
var SelectionReader = ref$4.SelectionReader; | ||
var selectionToDOM = ref$4.selectionToDOM; | ||
var ref = require("./dompos"); | ||
var scrollPosIntoView = ref.scrollPosIntoView; | ||
var posAtCoords = ref.posAtCoords; | ||
var coordsAtPos = ref.coordsAtPos; | ||
var ref$1 = require("./draw"); | ||
var draw = ref$1.draw; | ||
var redraw = ref$1.redraw; | ||
var ref$2 = require("./input"); | ||
var initInput = ref$2.initInput; | ||
var finishUpdateFromDOM = ref$2.finishUpdateFromDOM; | ||
var dispatchKeyDown = ref$2.dispatchKeyDown; | ||
var dispatchKeyPress = ref$2.dispatchKeyPress; | ||
var ref$3 = require("./selection"); | ||
var SelectionReader = ref$3.SelectionReader; | ||
var selectionToDOM = ref$3.selectionToDOM; | ||
var ref$4 = require("./decoration"); | ||
var viewDecorations = ref$4.viewDecorations; | ||
var addDummy = ref$4.addDummy;var assign; | ||
((assign = require("./decoration"), exports.Decoration = assign.Decoration, exports.DecorationSet = assign.DecorationSet)) | ||
@@ -31,2 +31,3 @@ // ::- An editor view manages the DOM structure that represents an | ||
this.state = this.drawnState = props.state | ||
this.dirty = null | ||
@@ -50,5 +51,4 @@ // :: dom.Node | ||
draw(this, this.state.doc) | ||
draw(this, this.state.doc, this.drawnDecorations = viewDecorations(this)) | ||
this.content.contentEditable = true | ||
this.dirtyNodes = new Map // Maps node object to 1 (re-scan content) or 2 (redraw entirely) | ||
@@ -91,5 +91,5 @@ this.lastSelectedNode = null | ||
if (docChange || this.dirtyNodes.size) { | ||
redraw(this, this.drawnState, state) | ||
this.dirtyNodes.clear() | ||
var decorations = viewDecorations(this) | ||
if (docChange || this.dirty || !decorations.sameOutput(this.drawnDecorations)) { | ||
this.redraw(state.doc, decorations) | ||
redrawn = true | ||
@@ -111,2 +111,16 @@ } | ||
EditorView.prototype.redraw = function redraw$1 (doc, decorations) { | ||
var oldDecorations = this.drawnDecorations, oldDoc = this.drawnState.doc | ||
this.drawnDecorations = decorations | ||
if (this.dirty) { | ||
var $start = oldDoc.resolve(this.dirty.from), $end = oldDoc.resolve(this.dirty.to), same = $start.sameDepth($end) | ||
this.dirty = null | ||
if (same == 0) | ||
{ return draw(this, doc, decorations) } | ||
oldDecorations = addDummy(decorations, doc, $start.before(same), $start.after(same)) | ||
} | ||
redraw(this, oldDoc, doc, oldDecorations, decorations) | ||
}; | ||
EditorView.prototype.updateDOMForProps = function updateDOMForProps () { | ||
@@ -171,24 +185,10 @@ var this$1 = this; | ||
EditorView.prototype.markRangeDirty = function markRangeDirty (from, to, doc) { | ||
var dirty = this.dirtyNodes | ||
var $from = doc.resolve(from), $to = doc.resolve(to) | ||
var same = $from.sameDepth($to) | ||
for (var depth = 0; depth <= same; depth++) { | ||
var child = $from.node(depth) | ||
if (!dirty.has(child)) { dirty.set(child, DIRTY_RESCAN) } | ||
} | ||
var start = $from.index(same), end = $to.indexAfter(same) | ||
var parent = $from.node(same) | ||
for (var i = start; i < end; i++) | ||
{ dirty.set(parent.child(i), DIRTY_REDRAW) } | ||
}; | ||
EditorView.prototype.markAllDirty = function markAllDirty () { | ||
this.dirtyNodes.set(this.doc, DIRTY_REDRAW) | ||
}; | ||
// :: ({left: number, top: number}) → ?number | ||
// :: ({left: number, top: number}) → ?{pos: number, inside: number} | ||
// Given a pair of coordinates, return the document position that | ||
// corresponds to them. May return null if the given coordinates | ||
// aren't inside of the visible editor. | ||
// aren't inside of the visible editor. When an object is returned, | ||
// its `pos` property is the position nearest to the coordinates, | ||
// and its `inside` property holds the position before the inner | ||
// node that the click happened inside of, or -1 if the click was at | ||
// the top level. | ||
EditorView.prototype.posAtCoords = function posAtCoords$1 (coords) { return posAtCoords(this, coords) }; | ||
@@ -321,2 +321,6 @@ | ||
// | ||
// decorations:: ?DecorationSet | ||
// A set of [document decorations](#view.Decoration) to add to the | ||
// view. | ||
// | ||
// spellcheck:: ?bool | ||
@@ -323,0 +327,0 @@ // Controls whether the DOM spellcheck attribute is enabled on the |
@@ -100,4 +100,5 @@ var ref = require("prosemirror-model"); | ||
function runHandlerOnContext(view, propName, pos, inLeaf, event) { | ||
var $pos = view.state.doc.resolve(inLeaf < 0 ? pos : inLeaf) | ||
function runHandlerOnContext(view, propName, pos, inside, event) { | ||
if (inside == -1) { return false } | ||
var $pos = view.state.doc.resolve(inside) | ||
var loop = function ( i ) { | ||
@@ -109,3 +110,3 @@ var node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i) | ||
for (var i = $pos.depth + (inLeaf < 0 ? 0 : 1); i > 0; i--) { | ||
for (var i = $pos.depth + 1; i > 0; i--) { | ||
var returned = loop( i ); | ||
@@ -123,11 +124,14 @@ | ||
function selectClickedLeaf(view, inLeaf) { | ||
var leaf = view.state.doc.nodeAt(inLeaf) | ||
if (leaf && leaf.isLeaf && isSelectable(leaf)) { | ||
updateSelection(view, new NodeSelection(view.state.doc.resolve(inLeaf))) | ||
function selectClickedLeaf(view, inside) { | ||
if (inside == -1) { return false } | ||
var $pos = view.state.doc.resolve(inside), node = $pos.nodeAfter | ||
if (node && node.isLeaf && isSelectable(node)) { | ||
updateSelection(view, new NodeSelection($pos)) | ||
return true | ||
} | ||
return false | ||
} | ||
function selectClickedNode(view, pos, inLeaf) { | ||
function selectClickedNode(view, inside) { | ||
if (inside == -1) { return false } | ||
var ref = view.state.selection; | ||
@@ -138,4 +142,4 @@ var selectedNode = ref.node; | ||
var $pos = view.state.doc.resolve(inLeaf < 0 ? pos : inLeaf) | ||
for (var i = $pos.depth + (inLeaf < 0 ? 0 : 1); i > 0; i--) { | ||
var $pos = view.state.doc.resolve(inside) | ||
for (var i = $pos.depth + 1; i > 0; i--) { | ||
var node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i) | ||
@@ -160,24 +164,33 @@ if (isSelectable(node)) { | ||
function handleSingleClick(view, pos, inLeaf, ctrl, event) { | ||
if (ctrl) { return selectClickedNode(view, pos, inLeaf) } | ||
function handleSingleClick(view, pos, inside, ctrl, event) { | ||
if (ctrl) { return selectClickedNode(view, inside) } | ||
return runHandlerOnContext(view, "handleClickOn", pos, inLeaf, event) || | ||
return runHandlerOnContext(view, "handleClickOn", pos, inside, event) || | ||
view.someProp("handleClick", function (f) { return f(view, pos, event); }) || | ||
inLeaf > -1 && selectClickedLeaf(view, inLeaf) | ||
selectClickedLeaf(view, inside) | ||
} | ||
function handleDoubleClick(view, pos, inLeaf, event) { | ||
return runHandlerOnContext(view, "handleDoubleClickOn", pos, inLeaf, event) || | ||
function handleDoubleClick(view, pos, inside, event) { | ||
return runHandlerOnContext(view, "handleDoubleClickOn", pos, inside, event) || | ||
view.someProp("handleDoubleClick", function (f) { return f(view, pos, event); }) | ||
} | ||
function handleTripleClick(view, pos, inLeaf, event) { | ||
return runHandlerOnContext(view, "handleTripleClickOn", pos, inLeaf, event) || | ||
function handleTripleClick(view, pos, inside, event) { | ||
return runHandlerOnContext(view, "handleTripleClickOn", pos, inside, event) || | ||
view.someProp("handleTripleClick", function (f) { return f(view, pos, event); }) || | ||
defaultTripleClick(view, pos, inLeaf) | ||
defaultTripleClick(view, inside) | ||
} | ||
function defaultTripleClick(view, pos, inLeaf) { | ||
var doc = view.state.doc, $pos = doc.resolve(inLeaf < 0 ? pos : inLeaf) | ||
for (var i = $pos.depth + (inLeaf < 0 ? 0 : 1); i > 0; i--) { | ||
function defaultTripleClick(view, inside) { | ||
var doc = view.state.doc | ||
if (inside == -1) { | ||
if (doc.isTextblock) { | ||
updateSelection(view, new TextSelection(doc.resolve(0), doc.resolve(doc.content.size))) | ||
return true | ||
} | ||
return false | ||
} | ||
var $pos = doc.resolve(inside) | ||
for (var i = $pos.depth + 1; i > 0; i--) { | ||
var node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i) | ||
@@ -187,3 +200,3 @@ var nodePos = $pos.before(i) | ||
{ updateSelection(view, new TextSelection(doc.resolve(nodePos + 1), | ||
doc.resolve(nodePos + 1 + node.content.size))) } | ||
doc.resolve(nodePos + 1 + node.content.size))) } | ||
else if (isSelectable(node)) | ||
@@ -206,3 +219,3 @@ { updateSelection(view, new NodeSelection(doc.resolve(nodePos))) } | ||
var now = Date.now(), type | ||
if (now - lastClick.time >= 500 || !isNear(event, lastClick)) { type = "singleClick" } | ||
if (now - lastClick.time >= 500 || !isNear(event, lastClick) || event.ctrlKey) { type = "singleClick" } | ||
else if (now - oneButLastClick.time >= 600 || !isNear(event, oneButLastClick)) { type = "doubleClick" } | ||
@@ -218,3 +231,3 @@ else { type = "tripleClick" } | ||
{ view.mouseDown = new MouseDown(view, pos, event, flushed) } | ||
else if ((type == "doubleClick" ? handleDoubleClick : handleTripleClick)(view, pos.pos, pos.inLeaf, event)) | ||
else if ((type == "doubleClick" ? handleDoubleClick : handleTripleClick)(view, pos.pos, pos.inside, event)) | ||
{ event.preventDefault() } | ||
@@ -230,8 +243,8 @@ else | ||
this.ctrlKey = event.ctrlKey | ||
this.allowDefault = view.shiftKey | ||
this.allowDefault = event.shiftKey | ||
var targetNode, targetPos | ||
if (pos.inLeaf > -1) { | ||
targetNode = view.state.doc.nodeAt(pos.inLeaf) | ||
targetPos = pos.inLeaf | ||
if (pos.inside > -1) { | ||
targetNode = view.state.doc.nodeAt(pos.inside) | ||
targetPos = pos.inside | ||
} else { | ||
@@ -274,3 +287,3 @@ var $pos = view.state.doc.resolve(pos.pos) | ||
this.view.selectionReader.fastPoll() | ||
} else if (handleSingleClick(this.view, this.pos.pos, this.pos.inLeaf, this.ctrlKey, event)) { | ||
} else if (handleSingleClick(this.view, this.pos.pos, this.pos.inside, this.ctrlKey, event)) { | ||
event.preventDefault() | ||
@@ -277,0 +290,0 @@ } else if (this.flushed) { |
@@ -12,2 +12,3 @@ var ref = require("prosemirror-state"); | ||
var coordsAtPos = ref$1.coordsAtPos; | ||
var childContainer = ref$1.childContainer; | ||
@@ -77,14 +78,10 @@ // Track the state of the current editor selection. Keeps the editor | ||
var domSel = this.view.root.getSelection(), doc = this.view.state.doc | ||
var ref = posFromDOM(domSel.focusNode, domSel.focusOffset); | ||
var head = ref.pos; | ||
var headInLeaf = ref.inLeaf; | ||
var $head = doc.resolve(head), $anchor, selection | ||
var domNode = domSel.focusNode, head = posFromDOM(domNode, domSel.focusOffset) | ||
var $head = doc.resolve(head), $anchor, selection, nodeAfter | ||
if (domSel.isCollapsed) { | ||
$anchor = $head | ||
if (headInLeaf > -1) { | ||
var $leaf = doc.resolve(headInLeaf), node = $leaf.nodeAfter | ||
if (isSelectable(node) && !node.type.isInline) { selection = new NodeSelection($leaf) } | ||
} | ||
if (!childContainer(domNode) && (nodeAfter = $head.nodeAfter) && nodeAfter.isLeaf && isSelectable(nodeAfter)) | ||
{ selection = new NodeSelection($head) } | ||
} else { | ||
$anchor = doc.resolve(posFromDOM(domSel.anchorNode, domSel.anchorOffset).pos) | ||
$anchor = doc.resolve(posFromDOM(domSel.anchorNode, domSel.anchorOffset)) | ||
} | ||
@@ -91,0 +88,0 @@ |
{ | ||
"name": "prosemirror-view", | ||
"version": "0.11.2", | ||
"version": "0.12.0", | ||
"description": "ProseMirror's view component", | ||
@@ -19,6 +19,7 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"prosemirror-model": "^0.11.0", | ||
"prosemirror-state": "^0.11.0" | ||
"prosemirror-model": "^0.12.0", | ||
"prosemirror-state": "^0.12.0" | ||
}, | ||
"devDependencies": { | ||
"prosemirror-transform": "^0.12.0", | ||
"buble": "~0.14.0", | ||
@@ -32,3 +33,3 @@ "ist": "^1.0.0", | ||
"test": "FIXME autorun browser tests", | ||
"test-server": "moduleserve test", | ||
"test-server": "moduleserve test --port 8090", | ||
"build": "rimraf dist && buble -i src -o dist", | ||
@@ -35,0 +36,0 @@ "link-src": "rimraf dist && ln -s src dist", |
@@ -18,3 +18,3 @@ const {Selection, NodeSelection, TextSelection, isSelectable} = require("prosemirror-state") | ||
function selectNodeHorizontally(view, dir) { | ||
function selectHorizontally(view, dir) { | ||
let {empty, node, $from, $to} = view.state.selection | ||
@@ -33,2 +33,3 @@ if (!empty && !node) return false | ||
return apply(view, new NodeSelection(dir < 0 ? view.state.doc.resolve($from.pos - nextNode.nodeSize) : $from)) | ||
;(dir < 0 ? skipIgnoredNodesLeft : skipIgnoredNodesRight)(view) | ||
return null | ||
@@ -45,2 +46,69 @@ } | ||
function nodeLen(node) { | ||
return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length | ||
} | ||
// Make sure the cursor isn't directly after one or more ignored | ||
// nodes, which will confuse the browser's cursor motion logic. | ||
function skipIgnoredNodesLeft(view) { | ||
let sel = view.root.getSelection(), moved = false | ||
let node = sel.anchorNode, offset = sel.anchorOffset | ||
for (;;) { | ||
if (offset > 0) { | ||
if (node.nodeType != 1) break | ||
let before = node.childNodes[offset - 1] | ||
if (before.nodeType == 1 && before.hasAttribute("pm-ignore")) { moved = true; offset-- } | ||
else break | ||
} else { | ||
let prev = node.previousSibling | ||
while (prev && prev.nodeType == 1 && prev.hasAttribute("pm-ignore")) moved = prev = prev.previousSibling | ||
if (!prev) { | ||
node = node.parentNode | ||
if (node == view.content) break | ||
offset = 0 | ||
} else { | ||
node = prev | ||
offset = nodeLen(node) | ||
} | ||
} | ||
} | ||
if (moved) setSel(sel, node, offset) | ||
} | ||
// Make sure the cursor isn't directly before one or more ignored | ||
// nodes. | ||
function skipIgnoredNodesRight(view) { | ||
let sel = view.root.getSelection(), moved = false | ||
let node = sel.anchorNode, offset = sel.anchorOffset, len = nodeLen(node) | ||
for (;;) { | ||
if (offset < len) { | ||
if (node.nodeType != 1) break | ||
let after = node.childNodes[offset] | ||
if (after.nodeType == 1 && after.hasAttribute("pm-ignore")) { moved = true; offset++ } | ||
else break | ||
} else { | ||
let next = node.nextSibling | ||
while (next && next.nodeType == 1 && next.hasAttribute("pm-ignore")) { moved = next = next.previousSibling } | ||
if (!next) { | ||
node = node.parentNode | ||
if (node == view.content) break | ||
offset = len = 0 | ||
} else { | ||
node = next | ||
offset = 0 | ||
len = nodeLen(node) | ||
} | ||
} | ||
} | ||
if (moved) setSel(sel, node, offset) | ||
} | ||
function setSel(sel, node, offset) { | ||
let range = document.createRange() | ||
range.setEnd(node, offset) | ||
range.setStart(node, offset) | ||
sel.removeAllRanges() | ||
sel.addRange(range) | ||
} | ||
// : (EditorState, number) | ||
@@ -50,3 +118,3 @@ // Check whether vertical selection motion would involve node | ||
// browser) | ||
function selectNodeVertically(view, dir) { | ||
function selectVertically(view, dir) { | ||
let {empty, node, $from, $to} = view.state.selection | ||
@@ -90,11 +158,11 @@ if (!empty && !node) return false | ||
} else if (code == 37) { // Left arrow | ||
return selectNodeHorizontally(view, -1) | ||
return selectHorizontally(view, -1) | ||
} else if (code == 39) { // Right arrow | ||
return selectNodeHorizontally(view, 1) | ||
return selectHorizontally(view, 1) | ||
} else if (code == 38) { // Up arrow | ||
return selectNodeVertically(view, -1) | ||
return selectVertically(view, -1) | ||
} else if (code == 40) { // Down arrow | ||
return selectNodeVertically(view, 1) | ||
return selectVertically(view, 1) | ||
} | ||
} | ||
exports.captureKeyDown = captureKeyDown |
@@ -132,3 +132,3 @@ const {Mark, DOMParser} = require("prosemirror-model") | ||
// Mark nodes touched by this change as 'to be redrawn' | ||
markDirtyFor(view, doc, change.start, change.endA) | ||
view.dirty = {from: change.start, to: change.endA} | ||
@@ -203,9 +203,1 @@ let $from = parsed.resolveNoCache(change.start - range.from) | ||
} | ||
function markDirtyFor(view, doc, start, end) { | ||
let $start = doc.resolve(start), $end = doc.resolve(end), same = $start.sameDepth($end) | ||
if (same == 0) | ||
view.markAllDirty() | ||
else | ||
view.markRangeDirty($start.before(same), $start.after(same), doc) | ||
} |
function isEditorContent(dom) { | ||
return dom.classList.contains("ProseMirror-content") | ||
return dom.nodeType == 1 && dom.classList.contains("ProseMirror-content") | ||
} | ||
// : (dom.Node) → number | ||
// Get the position before a given a DOM node in a document. | ||
// Get the position before a given a DOM node in a document. Returns | ||
// -1 when the first annotated parent node is the top-level document. | ||
function posBeforeFromDOM(node) { | ||
let pos = 0, add = 0 | ||
let pos = -1 | ||
for (let cur = node; !isEditorContent(cur); cur = cur.parentNode) { | ||
let attr = cur.getAttribute("pm-offset") | ||
if (attr) { pos += +attr + add; add = 1 } | ||
let attr = cur.nodeType == 1 && cur.getAttribute("pm-offset") | ||
if (attr) { pos += +attr + 1 } | ||
} | ||
@@ -16,5 +17,3 @@ return pos | ||
const posFromDOMResult = {pos: 0, inLeaf: -1} | ||
// : (dom.Node, number) → {pos: number, inLeaf: number} | ||
// : (dom.Node, number) → number | ||
function posFromDOM(dom, domOffset, bias = 0) { | ||
@@ -33,2 +32,4 @@ if (domOffset == null) { | ||
innerOffset += domOffset | ||
} else if (dom.nodeType != 1 || dom.hasAttribute("pm-ignore")) { | ||
innerOffset = 0 | ||
} else if (tag = dom.getAttribute("pm-offset") && !childContainer(dom)) { | ||
@@ -39,5 +40,3 @@ let size = +dom.getAttribute("pm-size") | ||
else innerOffset = Math.min(innerOffset, size) | ||
let inLeaf = posFromDOMResult.inLeaf = posBeforeFromDOM(dom) | ||
posFromDOMResult.pos = inLeaf + innerOffset | ||
return posFromDOMResult | ||
return posBeforeFromDOM(dom) + innerOffset | ||
} else if (dom.hasAttribute("pm-container")) { | ||
@@ -56,3 +55,3 @@ break | ||
let start = isEditorContent(dom) ? 0 : posBeforeFromDOM(dom) + 1, before = 0 | ||
let start = posBeforeFromDOM(dom) + 1, before = 0 | ||
@@ -66,5 +65,3 @@ for (let child = dom.childNodes[domOffset - 1]; child; child = child.previousSibling) { | ||
posFromDOMResult.inLeaf = -1 | ||
posFromDOMResult.pos = start + before + innerOffset | ||
return posFromDOMResult | ||
return start + before + innerOffset | ||
} | ||
@@ -75,3 +72,3 @@ exports.posFromDOM = posFromDOM | ||
function childContainer(dom) { | ||
return dom.hasAttribute("pm-container") ? dom : dom.querySelector("[pm-container]") | ||
return dom.nodeType != 1 ? null : dom.hasAttribute("pm-container") ? dom : dom.querySelector("[pm-container]") | ||
} | ||
@@ -271,3 +268,3 @@ exports.childContainer = childContainer | ||
} | ||
return posFromDOM(node, offset, bias) | ||
return {pos: posFromDOM(node, offset, bias), inside: posBeforeFromDOM(node)} | ||
} | ||
@@ -274,0 +271,0 @@ exports.posAtCoords = posAtCoords |
293
src/draw.js
@@ -6,111 +6,26 @@ const {DOMSerializer} = require("prosemirror-model") | ||
const DIRTY_RESCAN = 1, DIRTY_REDRAW = 2 | ||
exports.DIRTY_RESCAN = DIRTY_RESCAN; exports.DIRTY_REDRAW = DIRTY_REDRAW | ||
// FIXME track dirty ranges in a better way | ||
function setup(view) { | ||
let serializer = view.someProp("domSerializer") || DOMSerializer.fromSchema(view.state.schema) | ||
let options = { | ||
pos: 0, | ||
onRender(node, dom, _pos, offset) { | ||
if (node.isBlock) { | ||
if (offset != null) | ||
dom.setAttribute("pm-offset", offset) | ||
dom.setAttribute("pm-size", node.nodeSize) | ||
if (node.isTextblock) | ||
adjustTrailingHacks(serializer, dom, node) | ||
if (dom.contentEditable == "false") { | ||
let wrap = document.createElement("div") | ||
wrap.appendChild(dom) | ||
dom = wrap | ||
} | ||
} | ||
return dom | ||
}, | ||
onContainer(dom) { | ||
dom.setAttribute("pm-container", true) | ||
}, | ||
// : (Node, dom.Node, number, number) → dom.Node | ||
renderInlineFlat(node, dom, _pos, offset) { | ||
let inner = dom | ||
for (let i = 0; i < node.marks.length; i++) inner = inner.firstChild | ||
if (dom.nodeType != 1) { | ||
let wrap = document.createElement("span") | ||
wrap.appendChild(dom) | ||
dom = wrap | ||
} | ||
dom.setAttribute("pm-offset", offset) | ||
dom.setAttribute("pm-size", node.nodeSize) | ||
return dom | ||
}, | ||
document | ||
} | ||
return {serializer, options} | ||
function getSerializer(view) { | ||
return view.someProp("domSerializer") || DOMSerializer.fromSchema(view.state.schema) | ||
} | ||
function draw(view, doc) { | ||
function draw(view, doc, decorations) { | ||
view.content.textContent = "" | ||
let {options, serializer} = setup(view) | ||
view.content.appendChild(serializer.serializeFragment(doc.content, options)) | ||
new Context(getSerializer(view), decorations).serializeContent(doc, view.content) | ||
} | ||
exports.draw = draw | ||
function isBR(node, serializer) { | ||
if (!node.isLeaf || node.isText || !node.isInline) return false | ||
let ser = serializer.nodes[node.type.name](node) | ||
return Array.isArray(ser) ? ser[0] == "br" : ser && ser.nodeName == "BR" | ||
} | ||
function adjustTrailingHacks(serializer, dom, node) { | ||
let needs = node.content.size == 0 || isBR(node.lastChild, serializer) || | ||
(node.type.spec.code && node.lastChild.isText && /\n$/.test(node.lastChild.text)) | ||
? "br" : !node.lastChild.isText && node.lastChild.isLeaf ? "text" : null | ||
let last = dom.lastChild | ||
let has = !last || last.nodeType != 1 || !last.hasAttribute("pm-ignore") ? null | ||
: last.nodeName == "BR" ? "br" : "text" | ||
if (needs != has) { | ||
if (has) dom.removeChild(last) | ||
if (needs) { | ||
let add = document.createElement(needs == "br" ? "br" : "span") | ||
add.setAttribute("pm-ignore", needs == "br" ? "trailing-break" : "cursor-text") | ||
dom.appendChild(add) | ||
} | ||
} | ||
} | ||
function findNodeIn(parent, i, node) { | ||
for (; i < parent.childCount; i++) { | ||
let child = parent.child(i) | ||
if (child == node) return i | ||
} | ||
return -1 | ||
} | ||
function movePast(dom, view, onUnmount) { | ||
let next = dom.nextSibling | ||
for (let i = 0; i < onUnmount.length; i++) onUnmount[i](view, dom) | ||
dom.parentNode.removeChild(dom) | ||
return next | ||
} | ||
function redraw(view, oldState, newState) { | ||
let dirty = view.dirtyNodes | ||
if (dirty.get(oldState.doc) == DIRTY_REDRAW) return draw(view, newState.doc) | ||
let {serializer, options: opts} = setup(view) | ||
function redraw(view, oldDoc, newDoc, oldDecorations, newDecorations) { | ||
let serializer = getSerializer(view) | ||
let onUnmountDOM = [] | ||
view.someProp("onUnmountDOM", f => { onUnmountDOM.push(f) }) | ||
function scan(dom, node, prev, pos) { | ||
function scan(dom, node, prev, oldDecorations, newDecorations) { | ||
let iPrev = 0, oPrev = 0, pChild = prev.firstChild | ||
let domPos = dom.firstChild | ||
while (domPos && (domPos.nodeType != 1 || domPos.hasAttribute("pm-ignore"))) | ||
domPos = movePast(domPos, view, onUnmountDOM) | ||
let localDecorations = newDecorations.locals(node) | ||
let decoIndex = applyDecorations(localDecorations, 0, 0, 0, dom, domPos, false) | ||
function syncDOM() { | ||
@@ -126,5 +41,12 @@ while (domPos) { | ||
} | ||
let oldLocalDecorations, offset = 0, child | ||
function sameLocalDeco() { | ||
return compareDecorations(oldLocalDecorations || (oldLocalDecorations = oldDecorations.locals(prev)), | ||
localDecorations, decoIndex, | ||
oPrev, oPrev + pChild.nodeSize, offset, offset + child.nodeSize) | ||
} | ||
for (let iNode = 0, offset = 0; iNode < node.childCount; iNode++) { | ||
let child = node.child(iNode), matching, reuseDOM | ||
for (let iNode = 0; iNode < node.childCount; iNode++) { | ||
let matching, reuseDOM | ||
child = node.child(iNode) | ||
let found = pChild == child ? iPrev : findNodeIn(prev, iPrev + 1, child) | ||
@@ -139,15 +61,21 @@ if (found > -1) { | ||
if (matching && !dirty.get(matching) && syncDOM()) { | ||
let childDeco = newDecorations.forChild(offset, child), prevChildDeco, matchedLocalDeco | ||
if (matching && | ||
childDeco.sameOutput(prevChildDeco = oldDecorations.forChild(offset, child)) && | ||
(matchedLocalDeco = sameLocalDeco()) != null && | ||
syncDOM()) { | ||
reuseDOM = true | ||
} else if (pChild && !child.isText && child.sameMarkup(pChild) && dirty.get(pChild) != DIRTY_REDRAW && syncDOM()) { | ||
decoIndex = matchedLocalDeco | ||
} else if (pChild && !child.isText && child.sameMarkup(pChild) && | ||
(matchedLocalDeco = sameLocalDeco()) != null && syncDOM()) { | ||
reuseDOM = true | ||
decoIndex = matchedLocalDeco | ||
if (!pChild.isLeaf) | ||
scan(childContainer(domPos), child, pChild, pos + offset + 1) | ||
scan(childContainer(domPos), child, pChild, prevChildDeco || oldDecorations.forChild(oPrev, pChild), childDeco) | ||
domPos.setAttribute("pm-size", child.nodeSize) | ||
} else { | ||
opts.pos = pos + offset | ||
opts.offset = offset | ||
let rendered = serializer.serializeNode(child, opts) | ||
let rendered = new Context(serializer, childDeco).serialize(child, offset) | ||
dom.insertBefore(rendered, domPos) | ||
reuseDOM = false | ||
decoIndex = applyDecorations(localDecorations, decoIndex, offset, offset + child.nodeSize, dom, rendered) | ||
} | ||
@@ -171,2 +99,4 @@ | ||
pChild = prev.maybeChild(++iPrev) | ||
let end = offset + child.nodeSize | ||
decoIndex = applyDecorations(localDecorations, decoIndex, end, end, dom, domPos) | ||
} | ||
@@ -182,6 +112,157 @@ offset += child.nodeSize | ||
} | ||
scan(view.content, newState.doc, oldState.doc, 0) | ||
scan(view.content, newDoc, oldDoc, oldDecorations, newDecorations) | ||
} | ||
exports.redraw = redraw | ||
class Context { | ||
constructor(serializer, decorations) { | ||
this.serializer = serializer | ||
this.decorations = decorations | ||
} | ||
onContent(parent, target) { | ||
target.setAttribute("pm-container", true) | ||
this.serializeContent(parent, target, this.decorations) | ||
} | ||
serialize(node, offset) { | ||
let dom = this.serializer.serializeNodeAndMarks(node, this) | ||
if (dom.nodeType != 1 || dom.contentEditable == "false") { | ||
let wrap = document.createElement(node.isInline ? "span" : "div") | ||
wrap.appendChild(dom) | ||
dom = wrap | ||
} | ||
dom.setAttribute("pm-size", node.nodeSize) | ||
dom.setAttribute("pm-offset", offset) | ||
if (node.isTextblock) adjustTrailingHacks(this.serializer, dom, node) | ||
return dom | ||
} | ||
serializeContent(node, target) { | ||
let decorations = this.decorations | ||
let locals = decorations.locals(node) | ||
let i = applyDecorations(locals, 0, 0, 0, target, null, false) | ||
node.content.forEach((child, offset) => { | ||
this.decorations = decorations.forChild(offset, child) | ||
let dom = target.appendChild(this.serialize(child, offset)) | ||
i = applyDecorations(locals, i, offset, offset + child.nodeSize, target, dom) | ||
}) | ||
} | ||
} | ||
// : ([Decoration], number, number, number, dom.Node, ?dom.Node) → number | ||
// Used to apply decorations, either at a given point in a node that's | ||
// being updated, or those in and after a given child node. `i` is an | ||
// index into the local set of (non-overlapping) decorations, which is | ||
// used to avoid scanning through the array multiple times. | ||
// | ||
// When `from` == `to`, this should only draw inserted decorations at | ||
// the given position. When `from` < `to`, this should also decorate a | ||
// node. That node may be a text node, which may have different | ||
// decorations at different points, in which case it has to be split. | ||
// | ||
// `domNode` should be _the node after `from`_. That means that it is | ||
// the current node when `from` < `to`, and the node after the current | ||
// position when they are equal. It may be null, when `from` == `to` | ||
// and there are no nodes after the current point. | ||
// | ||
// Returns the updated index, which can be passed back to this | ||
// function later. | ||
function applyDecorations(locals, i, from, to, domParent, domNode) { | ||
let result = i | ||
for (; i < locals.length; i++) { | ||
let span = locals[i] | ||
if (span.from > to || (span.from == to && span.to > to)) break | ||
if (from < span.from) { | ||
domNode = span.from < to ? splitText(domNode, span.from - from) : domNode.nextSibling | ||
from = span.from | ||
} | ||
let curNode = domNode | ||
if (span.to < to && span.from < span.to) { | ||
domNode = splitText(domNode, span.to - from) | ||
from = span.to | ||
} | ||
for (;;) { | ||
curNode = span.type.apply(domParent, curNode) | ||
if (i < locals.length - 1 && locals[i + 1].to == span.to && locals[i + 1].from == span.from) span = locals[++i] | ||
else break | ||
} | ||
if (span.to <= to) result = i + 1 | ||
} | ||
return result | ||
} | ||
function compareDecorations(old, cur, i, oldFrom, oldTo, curFrom, curTo) { | ||
let j = 0, result = i | ||
while (j < old.length && old[j].to <= oldFrom) j++ | ||
for (;; i++, j++) { | ||
let oldEnd = j == old.length || old[j].from >= oldTo | ||
if (i == cur.length || cur[i].from >= curTo) return oldEnd ? result : null | ||
else if (oldEnd) return null | ||
let oldNext = old[j], curNext = cur[i] | ||
if (oldNext.type != curNext.type || | ||
oldNext.from - oldFrom != curNext.from - curFrom || | ||
oldNext.to - oldFrom != curNext.to - curFrom) return null | ||
if (curNext.to <= curTo) result = i + 1 | ||
} | ||
} | ||
function splitText(node, offset) { | ||
let inner = node | ||
while (inner.nodeType != 3) inner = inner.firstChild | ||
let newNode = document.createTextNode(inner.nodeValue.slice(offset)) | ||
inner.nodeValue = inner.nodeValue.slice(0, offset) | ||
while (inner != node) { | ||
let parent = inner.parentNode, wrap = parent.cloneNode(false) | ||
wrap.appendChild(newNode) | ||
newNode = wrap | ||
inner = parent | ||
} | ||
node.parentNode.insertBefore(newNode, node.nextSibling) | ||
let size = +node.getAttribute("pm-size") | ||
newNode.setAttribute("pm-size", size - offset) | ||
node.setAttribute("pm-size", offset) | ||
newNode.setAttribute("pm-offset", +node.getAttribute("pm-offset") + offset) | ||
return newNode | ||
} | ||
function findNodeIn(parent, i, node) { | ||
for (; i < parent.childCount; i++) { | ||
let child = parent.child(i) | ||
if (child == node) return i | ||
} | ||
return -1 | ||
} | ||
function movePast(dom, view, onUnmount) { | ||
let next = dom.nextSibling | ||
for (let i = 0; i < onUnmount.length; i++) onUnmount[i](view, dom) | ||
dom.parentNode.removeChild(dom) | ||
return next | ||
} | ||
function isBR(node, serializer) { | ||
if (!node.isLeaf || node.isText || !node.isInline) return false | ||
let ser = serializer.nodes[node.type.name](node) | ||
return Array.isArray(ser) ? ser[0] == "br" : ser && ser.nodeName == "BR" | ||
} | ||
function adjustTrailingHacks(serializer, dom, node) { | ||
let needs = node.content.size == 0 || isBR(node.lastChild, serializer) || | ||
(node.type.spec.code && node.lastChild.isText && /\n$/.test(node.lastChild.text)) | ||
? "br" : !node.lastChild.isText && node.lastChild.isLeaf ? "text" : null | ||
let last = dom.lastChild | ||
let has = !last || last.nodeType != 1 || !last.hasAttribute("pm-ignore") ? null | ||
: last.nodeName == "BR" ? "br" : "text" | ||
if (needs != has) { | ||
if (has == "br") dom.removeChild(last) | ||
if (needs) { | ||
let add = document.createElement(needs == "br" ? "br" : "span") | ||
add.setAttribute("pm-ignore", needs == "br" ? "trailing-break" : "cursor-text") | ||
dom.appendChild(add) | ||
} | ||
} | ||
} | ||
function iosHacks(dom) { | ||
@@ -188,0 +269,0 @@ if (dom.nodeName == "UL" || dom.nodeName == "OL") { |
@@ -1,7 +0,9 @@ | ||
const {Map} = require("./map") | ||
const {scrollPosIntoView, posAtCoords, coordsAtPos} = require("./dompos") | ||
const {draw, redraw, DIRTY_REDRAW, DIRTY_RESCAN} = require("./draw") | ||
const {draw, redraw} = require("./draw") | ||
const {initInput, finishUpdateFromDOM, dispatchKeyDown, dispatchKeyPress} = require("./input") | ||
const {SelectionReader, selectionToDOM} = require("./selection") | ||
const {viewDecorations, addDummy} = require("./decoration") | ||
;({Decoration: exports.Decoration, DecorationSet: exports.DecorationSet} = require("./decoration")) | ||
// ::- An editor view manages the DOM structure that represents an | ||
@@ -23,2 +25,3 @@ // editor. Its state and behavior are determined by its | ||
this.state = this.drawnState = props.state | ||
this.dirty = null | ||
@@ -42,5 +45,4 @@ // :: dom.Node | ||
draw(this, this.state.doc) | ||
draw(this, this.state.doc, this.drawnDecorations = viewDecorations(this)) | ||
this.content.contentEditable = true | ||
this.dirtyNodes = new Map // Maps node object to 1 (re-scan content) or 2 (redraw entirely) | ||
@@ -79,5 +81,5 @@ this.lastSelectedNode = null | ||
if (docChange || this.dirtyNodes.size) { | ||
redraw(this, this.drawnState, state) | ||
this.dirtyNodes.clear() | ||
let decorations = viewDecorations(this) | ||
if (docChange || this.dirty || !decorations.sameOutput(this.drawnDecorations)) { | ||
this.redraw(state.doc, decorations) | ||
redrawn = true | ||
@@ -99,2 +101,16 @@ } | ||
redraw(doc, decorations) { | ||
let oldDecorations = this.drawnDecorations, oldDoc = this.drawnState.doc | ||
this.drawnDecorations = decorations | ||
if (this.dirty) { | ||
let $start = oldDoc.resolve(this.dirty.from), $end = oldDoc.resolve(this.dirty.to), same = $start.sameDepth($end) | ||
this.dirty = null | ||
if (same == 0) | ||
return draw(this, doc, decorations) | ||
oldDecorations = addDummy(decorations, doc, $start.before(same), $start.after(same)) | ||
} | ||
redraw(this, oldDoc, doc, oldDecorations, decorations) | ||
} | ||
updateDOMForProps() { | ||
@@ -155,24 +171,10 @@ let spellcheck = !!this.someProp("spellcheck") | ||
markRangeDirty(from, to, doc) { | ||
let dirty = this.dirtyNodes | ||
let $from = doc.resolve(from), $to = doc.resolve(to) | ||
let same = $from.sameDepth($to) | ||
for (let depth = 0; depth <= same; depth++) { | ||
let child = $from.node(depth) | ||
if (!dirty.has(child)) dirty.set(child, DIRTY_RESCAN) | ||
} | ||
let start = $from.index(same), end = $to.indexAfter(same) | ||
let parent = $from.node(same) | ||
for (let i = start; i < end; i++) | ||
dirty.set(parent.child(i), DIRTY_REDRAW) | ||
} | ||
markAllDirty() { | ||
this.dirtyNodes.set(this.doc, DIRTY_REDRAW) | ||
} | ||
// :: ({left: number, top: number}) → ?number | ||
// :: ({left: number, top: number}) → ?{pos: number, inside: number} | ||
// Given a pair of coordinates, return the document position that | ||
// corresponds to them. May return null if the given coordinates | ||
// aren't inside of the visible editor. | ||
// aren't inside of the visible editor. When an object is returned, | ||
// its `pos` property is the position nearest to the coordinates, | ||
// and its `inside` property holds the position before the inner | ||
// node that the click happened inside of, or -1 if the click was at | ||
// the top level. | ||
posAtCoords(coords) { return posAtCoords(this, coords) } | ||
@@ -304,2 +306,6 @@ | ||
// | ||
// decorations:: ?DecorationSet | ||
// A set of [document decorations](#view.Decoration) to add to the | ||
// view. | ||
// | ||
// spellcheck:: ?bool | ||
@@ -306,0 +312,0 @@ // Controls whether the DOM spellcheck attribute is enabled on the |
@@ -85,5 +85,6 @@ const {Slice, Fragment, DOMParser, DOMSerializer} = require("prosemirror-model") | ||
function runHandlerOnContext(view, propName, pos, inLeaf, event) { | ||
let $pos = view.state.doc.resolve(inLeaf < 0 ? pos : inLeaf) | ||
for (let i = $pos.depth + (inLeaf < 0 ? 0 : 1); i > 0; i--) { | ||
function runHandlerOnContext(view, propName, pos, inside, event) { | ||
if (inside == -1) return false | ||
let $pos = view.state.doc.resolve(inside) | ||
for (let i = $pos.depth + 1; i > 0; i--) { | ||
let node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i) | ||
@@ -101,15 +102,18 @@ if (view.someProp(propName, f => f(view, pos, node, $pos.before(i), event))) | ||
function selectClickedLeaf(view, inLeaf) { | ||
let leaf = view.state.doc.nodeAt(inLeaf) | ||
if (leaf && leaf.isLeaf && isSelectable(leaf)) { | ||
updateSelection(view, new NodeSelection(view.state.doc.resolve(inLeaf))) | ||
function selectClickedLeaf(view, inside) { | ||
if (inside == -1) return false | ||
let $pos = view.state.doc.resolve(inside), node = $pos.nodeAfter | ||
if (node && node.isLeaf && isSelectable(node)) { | ||
updateSelection(view, new NodeSelection($pos)) | ||
return true | ||
} | ||
return false | ||
} | ||
function selectClickedNode(view, pos, inLeaf) { | ||
function selectClickedNode(view, inside) { | ||
if (inside == -1) return false | ||
let {node: selectedNode, $from} = view.state.selection, selectAt | ||
let $pos = view.state.doc.resolve(inLeaf < 0 ? pos : inLeaf) | ||
for (let i = $pos.depth + (inLeaf < 0 ? 0 : 1); i > 0; i--) { | ||
let $pos = view.state.doc.resolve(inside) | ||
for (let i = $pos.depth + 1; i > 0; i--) { | ||
let node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i) | ||
@@ -134,24 +138,33 @@ if (isSelectable(node)) { | ||
function handleSingleClick(view, pos, inLeaf, ctrl, event) { | ||
if (ctrl) return selectClickedNode(view, pos, inLeaf) | ||
function handleSingleClick(view, pos, inside, ctrl, event) { | ||
if (ctrl) return selectClickedNode(view, inside) | ||
return runHandlerOnContext(view, "handleClickOn", pos, inLeaf, event) || | ||
return runHandlerOnContext(view, "handleClickOn", pos, inside, event) || | ||
view.someProp("handleClick", f => f(view, pos, event)) || | ||
inLeaf > -1 && selectClickedLeaf(view, inLeaf) | ||
selectClickedLeaf(view, inside) | ||
} | ||
function handleDoubleClick(view, pos, inLeaf, event) { | ||
return runHandlerOnContext(view, "handleDoubleClickOn", pos, inLeaf, event) || | ||
function handleDoubleClick(view, pos, inside, event) { | ||
return runHandlerOnContext(view, "handleDoubleClickOn", pos, inside, event) || | ||
view.someProp("handleDoubleClick", f => f(view, pos, event)) | ||
} | ||
function handleTripleClick(view, pos, inLeaf, event) { | ||
return runHandlerOnContext(view, "handleTripleClickOn", pos, inLeaf, event) || | ||
function handleTripleClick(view, pos, inside, event) { | ||
return runHandlerOnContext(view, "handleTripleClickOn", pos, inside, event) || | ||
view.someProp("handleTripleClick", f => f(view, pos, event)) || | ||
defaultTripleClick(view, pos, inLeaf) | ||
defaultTripleClick(view, inside) | ||
} | ||
function defaultTripleClick(view, pos, inLeaf) { | ||
let doc = view.state.doc, $pos = doc.resolve(inLeaf < 0 ? pos : inLeaf) | ||
for (let i = $pos.depth + (inLeaf < 0 ? 0 : 1); i > 0; i--) { | ||
function defaultTripleClick(view, inside) { | ||
let doc = view.state.doc | ||
if (inside == -1) { | ||
if (doc.isTextblock) { | ||
updateSelection(view, new TextSelection(doc.resolve(0), doc.resolve(doc.content.size))) | ||
return true | ||
} | ||
return false | ||
} | ||
let $pos = doc.resolve(inside) | ||
for (let i = $pos.depth + 1; i > 0; i--) { | ||
let node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i) | ||
@@ -161,3 +174,3 @@ let nodePos = $pos.before(i) | ||
updateSelection(view, new TextSelection(doc.resolve(nodePos + 1), | ||
doc.resolve(nodePos + 1 + node.content.size))) | ||
doc.resolve(nodePos + 1 + node.content.size))) | ||
else if (isSelectable(node)) | ||
@@ -180,3 +193,3 @@ updateSelection(view, new NodeSelection(doc.resolve(nodePos))) | ||
let now = Date.now(), type | ||
if (now - lastClick.time >= 500 || !isNear(event, lastClick)) type = "singleClick" | ||
if (now - lastClick.time >= 500 || !isNear(event, lastClick) || event.ctrlKey) type = "singleClick" | ||
else if (now - oneButLastClick.time >= 600 || !isNear(event, oneButLastClick)) type = "doubleClick" | ||
@@ -192,3 +205,3 @@ else type = "tripleClick" | ||
view.mouseDown = new MouseDown(view, pos, event, flushed) | ||
else if ((type == "doubleClick" ? handleDoubleClick : handleTripleClick)(view, pos.pos, pos.inLeaf, event)) | ||
else if ((type == "doubleClick" ? handleDoubleClick : handleTripleClick)(view, pos.pos, pos.inside, event)) | ||
event.preventDefault() | ||
@@ -205,8 +218,8 @@ else | ||
this.ctrlKey = event.ctrlKey | ||
this.allowDefault = view.shiftKey | ||
this.allowDefault = event.shiftKey | ||
let targetNode, targetPos | ||
if (pos.inLeaf > -1) { | ||
targetNode = view.state.doc.nodeAt(pos.inLeaf) | ||
targetPos = pos.inLeaf | ||
if (pos.inside > -1) { | ||
targetNode = view.state.doc.nodeAt(pos.inside) | ||
targetPos = pos.inside | ||
} else { | ||
@@ -249,3 +262,3 @@ let $pos = view.state.doc.resolve(pos.pos) | ||
this.view.selectionReader.fastPoll() | ||
} else if (handleSingleClick(this.view, this.pos.pos, this.pos.inLeaf, this.ctrlKey, event)) { | ||
} else if (handleSingleClick(this.view, this.pos.pos, this.pos.inside, this.ctrlKey, event)) { | ||
event.preventDefault() | ||
@@ -252,0 +265,0 @@ } else if (this.flushed) { |
@@ -10,1 +10,7 @@ ProseMirror's _view_ displays a given editor state in the DOM, and | ||
@EditorProps | ||
@Decoration | ||
@DecorationAttrs | ||
@DecorationSet |
const {Selection, NodeSelection, isSelectable} = require("prosemirror-state") | ||
const browser = require("./browser") | ||
const {posFromDOM, DOMAfterPos, DOMFromPos, coordsAtPos} = require("./dompos") | ||
const {posFromDOM, DOMAfterPos, DOMFromPos, coordsAtPos, childContainer} = require("./dompos") | ||
@@ -68,12 +68,10 @@ // Track the state of the current editor selection. Keeps the editor | ||
let domSel = this.view.root.getSelection(), doc = this.view.state.doc | ||
let {pos: head, inLeaf: headInLeaf} = posFromDOM(domSel.focusNode, domSel.focusOffset) | ||
let $head = doc.resolve(head), $anchor, selection | ||
let domNode = domSel.focusNode, head = posFromDOM(domNode, domSel.focusOffset) | ||
let $head = doc.resolve(head), $anchor, selection, nodeAfter | ||
if (domSel.isCollapsed) { | ||
$anchor = $head | ||
if (headInLeaf > -1) { | ||
let $leaf = doc.resolve(headInLeaf), node = $leaf.nodeAfter | ||
if (isSelectable(node) && !node.type.isInline) selection = new NodeSelection($leaf) | ||
} | ||
if (!childContainer(domNode) && (nodeAfter = $head.nodeAfter) && nodeAfter.isLeaf && isSelectable(nodeAfter)) | ||
selection = new NodeSelection($head) | ||
} else { | ||
$anchor = doc.resolve(posFromDOM(domSel.anchorNode, domSel.anchorOffset).pos) | ||
$anchor = doc.resolve(posFromDOM(domSel.anchorNode, domSel.anchorOffset)) | ||
} | ||
@@ -80,0 +78,0 @@ |
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
206031
4893
6
+ Addedprosemirror-model@0.12.0(transitive)
+ Addedprosemirror-state@0.12.0(transitive)
+ Addedprosemirror-transform@0.12.1(transitive)
- Removedprosemirror-model@0.11.1(transitive)
- Removedprosemirror-state@0.11.1(transitive)
- Removedprosemirror-transform@0.11.1(transitive)
Updatedprosemirror-model@^0.12.0
Updatedprosemirror-state@^0.12.0