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

prosemirror-view

Package Overview
Dependencies
Maintainers
1
Versions
282
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

prosemirror-view - npm Package Compare versions

Comparing version 0.11.2 to 0.12.0

dist/decoration.js

82

dist/capturekeys.js

@@ -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

10

dist/domchange.js

@@ -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

@@ -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

@@ -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 @@

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc