prosemirror-view
Advanced tools
Comparing version 0.15.2 to 0.16.0
@@ -75,2 +75,4 @@ var ref = require("prosemirror-state"); | ||
else { break } | ||
} else if (isBlockNode(node)) { | ||
break | ||
} else { | ||
@@ -111,2 +113,4 @@ var prev = node.previousSibling | ||
else { break } | ||
} else if (isBlockNode(node)) { | ||
break | ||
} else { | ||
@@ -117,3 +121,3 @@ var next = node.nextSibling | ||
moveOffset = Array.prototype.indexOf.call(moveNode.childNodes, next) + 1 | ||
next = next.previousSibling | ||
next = next.nextSibling | ||
} | ||
@@ -134,2 +138,7 @@ if (!next) { | ||
function isBlockNode(dom) { | ||
var desc = dom.pmViewDesc | ||
return desc && desc.node && desc.node.isBlock | ||
} | ||
function setSel(sel, node, offset) { | ||
@@ -136,0 +145,0 @@ var range = document.createRange() |
@@ -8,3 +8,3 @@ function compareObjs(a, b) { | ||
var WidgetType = function WidgetType(widget, options) { | ||
var WidgetType = function(widget, options) { | ||
if (widget.nodeType != 1) { | ||
@@ -21,3 +21,3 @@ var wrap = document.createElement("span") | ||
WidgetType.prototype.map = function map (mapping, span, offset, oldOffset) { | ||
WidgetType.prototype.map = function (mapping, span, offset, oldOffset) { | ||
var ref = mapping.mapResult(span.from + oldOffset); | ||
@@ -29,5 +29,5 @@ var pos = ref.pos; | ||
WidgetType.prototype.valid = function valid () { return true }; | ||
WidgetType.prototype.valid = function () { return true }; | ||
WidgetType.prototype.eq = function eq (other) { | ||
WidgetType.prototype.eq = function (other) { | ||
return this == other || | ||
@@ -38,3 +38,3 @@ (other instanceof WidgetType && (this.widget == other.widget || this.options.key) && | ||
var InlineType = function InlineType(attrs, options) { | ||
var InlineType = function(attrs, options) { | ||
this.options = options || noOptions | ||
@@ -44,3 +44,3 @@ this.attrs = attrs | ||
InlineType.prototype.map = function map (mapping, span, offset, oldOffset) { | ||
InlineType.prototype.map = function (mapping, span, offset, oldOffset) { | ||
var from = mapping.map(span.from + oldOffset, this.options.inclusiveLeft ? -1 : 1) - offset | ||
@@ -51,5 +51,5 @@ var to = mapping.map(span.to + oldOffset, this.options.inclusiveRight ? 1 : -1) - offset | ||
InlineType.prototype.valid = function valid (_, span) { return span.from < span.to }; | ||
InlineType.prototype.valid = function (_, span) { return span.from < span.to }; | ||
InlineType.prototype.eq = function eq (other) { | ||
InlineType.prototype.eq = function (other) { | ||
return this == other || | ||
@@ -60,5 +60,5 @@ (other instanceof InlineType && compareObjs(this.attrs, other.attrs) && | ||
InlineType.is = function is (span) { return span.type instanceof InlineType }; | ||
InlineType.is = function (span) { return span.type instanceof InlineType }; | ||
var NodeType = function NodeType(attrs, options) { | ||
var NodeType = function(attrs, options) { | ||
this.attrs = attrs | ||
@@ -68,3 +68,3 @@ this.options = options || noOptions | ||
NodeType.prototype.map = function map (mapping, span, offset, oldOffset) { | ||
NodeType.prototype.map = function (mapping, span, offset, oldOffset) { | ||
var from = mapping.mapResult(span.from + oldOffset, 1) | ||
@@ -77,3 +77,3 @@ if (from.deleted) { return null } | ||
NodeType.prototype.valid = function valid (node, span) { | ||
NodeType.prototype.valid = function (node, span) { | ||
var ref = node.content.findIndex(span.from); | ||
@@ -85,3 +85,3 @@ var index = ref.index; | ||
NodeType.prototype.eq = function eq (other) { | ||
NodeType.prototype.eq = function (other) { | ||
return this == other || | ||
@@ -96,3 +96,3 @@ (other instanceof NodeType && compareObjs(this.attrs, other.attrs) && | ||
// static members of this class for details. | ||
var Decoration = function Decoration(from, to, type) { | ||
var Decoration = function(from, to, type) { | ||
this.from = from | ||
@@ -105,11 +105,11 @@ this.to = to | ||
Decoration.prototype.copy = function copy (from, to) { | ||
Decoration.prototype.copy = function (from, to) { | ||
return new Decoration(from, to, this.type) | ||
}; | ||
Decoration.prototype.eq = function eq (other) { | ||
Decoration.prototype.eq = function (other) { | ||
return this.type.eq(other.type) && this.from == other.from && this.to == other.to | ||
}; | ||
Decoration.prototype.map = function map (mapping, offset, oldOffset) { | ||
Decoration.prototype.map = function (mapping, offset, oldOffset) { | ||
return this.type.map(mapping, this, offset, oldOffset) | ||
@@ -135,4 +135,4 @@ }; | ||
// and reuse DOM nodes. | ||
Decoration.widget = function widget (pos, widget, options) { | ||
return new Decoration(pos, pos, new WidgetType(widget, options)) | ||
Decoration.widget = function (pos, dom, options) { | ||
return new Decoration(pos, pos, new WidgetType(dom, options)) | ||
}; | ||
@@ -157,3 +157,3 @@ | ||
// [`inclusiveLeft`](#view.Decoration^inline^options.inclusiveLeft). | ||
Decoration.inline = function inline (from, to, attrs, options) { | ||
Decoration.inline = function (from, to, attrs, options) { | ||
return new Decoration(from, to, new InlineType(attrs, options)) | ||
@@ -166,3 +166,3 @@ }; | ||
// node, will receive the given attributes. | ||
Decoration.node = function node (from, to, attrs, options) { | ||
Decoration.node = function (from, to, attrs, options) { | ||
return new Decoration(from, to, new NodeType(attrs, options)) | ||
@@ -201,3 +201,3 @@ }; | ||
// modified, updates create a new value. | ||
var DecorationSet = function DecorationSet(local, children) { | ||
var DecorationSet = function(local, children) { | ||
this.local = local && local.length ? local : none | ||
@@ -210,3 +210,3 @@ this.children = children && children.length ? children : none | ||
// document. | ||
DecorationSet.create = function create (doc, decorations) { | ||
DecorationSet.create = function (doc, decorations) { | ||
return decorations.length ? buildTree(decorations, doc, 0, noOptions) : empty | ||
@@ -220,3 +220,3 @@ }; | ||
// the set are collected. | ||
DecorationSet.prototype.find = function find (start, end) { | ||
DecorationSet.prototype.find = function (start, end) { | ||
var result = [] | ||
@@ -227,3 +227,3 @@ this.findInner(start == null ? 0 : start, end == null ? 1e9 : end, result, 0) | ||
DecorationSet.prototype.findInner = function findInner (start, end, result, offset) { | ||
DecorationSet.prototype.findInner = function (start, end, result, offset) { | ||
var this$1 = this; | ||
@@ -254,3 +254,3 @@ | ||
// options of that decoration. | ||
DecorationSet.prototype.map = function map (mapping, doc, options) { | ||
DecorationSet.prototype.map = function (mapping, doc, options) { | ||
if (this == empty) { return this } | ||
@@ -260,3 +260,3 @@ return this.mapInner(mapping, doc, 0, 0, options || noOptions) | ||
DecorationSet.prototype.mapInner = function mapInner (mapping, node, offset, oldOffset, options) { | ||
DecorationSet.prototype.mapInner = function (mapping, node, offset, oldOffset, options) { | ||
var this$1 = this; | ||
@@ -281,3 +281,3 @@ | ||
// create the appropriate tree structure. | ||
DecorationSet.prototype.add = function add (doc, decorations) { | ||
DecorationSet.prototype.add = function (doc, decorations) { | ||
if (!decorations.length) { return this } | ||
@@ -288,3 +288,3 @@ if (this == empty) { return DecorationSet.create(doc, decorations) } | ||
DecorationSet.prototype.addInner = function addInner (doc, decorations, offset) { | ||
DecorationSet.prototype.addInner = function (doc, decorations, offset) { | ||
var this$1 = this; | ||
@@ -314,3 +314,3 @@ | ||
// the ones in the given array. | ||
DecorationSet.prototype.remove = function remove (decorations) { | ||
DecorationSet.prototype.remove = function (decorations) { | ||
if (decorations.length == 0 || this == empty) { return this } | ||
@@ -320,3 +320,3 @@ return this.removeInner(decorations, 0) | ||
DecorationSet.prototype.removeInner = function removeInner (decorations, offset) { | ||
DecorationSet.prototype.removeInner = function (decorations, offset) { | ||
var this$1 = this; | ||
@@ -353,3 +353,3 @@ | ||
DecorationSet.prototype.forChild = function forChild (offset, node) { | ||
DecorationSet.prototype.forChild = function (offset, node) { | ||
var this$1 = this; | ||
@@ -380,3 +380,3 @@ | ||
DecorationSet.prototype.eq = function eq (other) { | ||
DecorationSet.prototype.eq = function (other) { | ||
var this$1 = this; | ||
@@ -397,7 +397,7 @@ | ||
DecorationSet.prototype.locals = function locals (node) { | ||
DecorationSet.prototype.locals = function (node) { | ||
return removeOverlap(this.localsInner(node)) | ||
}; | ||
DecorationSet.prototype.localsInner = function localsInner (node) { | ||
DecorationSet.prototype.localsInner = function (node) { | ||
var this$1 = this; | ||
@@ -422,7 +422,7 @@ | ||
var DecorationGroup = function DecorationGroup(members) { | ||
var DecorationGroup = function(members) { | ||
this.members = members | ||
}; | ||
DecorationGroup.prototype.forChild = function forChild (offset, child) { | ||
DecorationGroup.prototype.forChild = function (offset, child) { | ||
var this$1 = this; | ||
@@ -441,3 +441,3 @@ | ||
DecorationGroup.prototype.eq = function eq (other) { | ||
DecorationGroup.prototype.eq = function (other) { | ||
var this$1 = this; | ||
@@ -452,3 +452,3 @@ | ||
DecorationGroup.prototype.locals = function locals (node) { | ||
DecorationGroup.prototype.locals = function (node) { | ||
var this$1 = this; | ||
@@ -473,3 +473,3 @@ | ||
DecorationGroup.from = function from (members) { | ||
DecorationGroup.from = function (members) { | ||
switch (members.length) { | ||
@@ -476,0 +476,0 @@ case 0: return empty |
@@ -10,3 +10,3 @@ var ref = require("prosemirror-model"); | ||
var DOMChange = function DOMChange(view, id, composing) { | ||
var DOMChange = function(view, id, composing) { | ||
var this$1 = this; | ||
@@ -23,3 +23,3 @@ | ||
DOMChange.prototype.addRange = function addRange (from, to) { | ||
DOMChange.prototype.addRange = function (from, to) { | ||
if (this.from == null) { | ||
@@ -34,3 +34,3 @@ this.from = from | ||
DOMChange.prototype.changedRange = function changedRange () { | ||
DOMChange.prototype.changedRange = function () { | ||
if (this.from == null) { return rangeAroundSelection(this.state.selection) } | ||
@@ -42,7 +42,3 @@ var $from = this.state.doc.resolve(this.from), $to = this.state.doc.resolve(this.to) | ||
DOMChange.prototype.read = function read (range) { | ||
readDOMChange(this, this.state, range) | ||
}; | ||
DOMChange.prototype.finish = function finish (force) { | ||
DOMChange.prototype.finish = function (force) { | ||
clearTimeout(this.timeout) | ||
@@ -54,6 +50,9 @@ if (this.composing && !force) { return } | ||
this.view.inDOMChange = null | ||
this.read(range) | ||
readDOMChange(this, this.state, range) | ||
// If the reading didn't result in a view update, force one by | ||
// resetting the view to its current state. | ||
if (this.view.docView.dirty) { this.view.updateState(this.view.state) } | ||
}; | ||
DOMChange.prototype.compositionEnd = function compositionEnd () { | ||
DOMChange.prototype.compositionEnd = function () { | ||
var this$1 = this; | ||
@@ -67,3 +66,3 @@ | ||
DOMChange.start = function start (view, composing) { | ||
DOMChange.start = function (view, composing) { | ||
if (view.inDOMChange) { | ||
@@ -192,2 +191,8 @@ if (composing) { | ||
var parseResult, doc = oldState.doc, view = domChange.view | ||
// If there have been changes since this DOM update started, we must | ||
// map our start and end positions, as well as the new selection | ||
// positions, through them. | ||
var mapping = domChange.mappings.getMapping(view.state) | ||
if (!mapping) { return } | ||
for (;;) { | ||
@@ -205,4 +210,11 @@ parseResult = parseBetween(view, oldState, range.from, range.to) | ||
var change = findDiff(compare.content, parsed.content, range.from, oldState.selection.from) | ||
if (!change) { return false } | ||
if (!change) { | ||
if (parsedSel) { | ||
var sel = resolveSelection(view.state.doc, mapping, parsedSel) | ||
if (!sel.eq(view.state.selection)) { view.props.onAction(sel.action()) } | ||
} | ||
return | ||
} | ||
var $from = parsed.resolveNoCache(change.start - range.from) | ||
@@ -223,15 +235,5 @@ var $to = parsed.resolveNoCache(change.endB - range.from) | ||
var from = change.start, to = change.endA | ||
// If there have been changes since this DOM update started, we must | ||
// map our start and end positions, as well as the new selection | ||
// positions, through them. | ||
var mapping = domChange.mappings.getMapping(view.state), $from1 | ||
if (!mapping) { return } | ||
var from = mapping.map(change.start), to = mapping.map(change.endA, -1) | ||
from = mapping.map(from) | ||
to = mapping.map(to) | ||
if (parsedSel) { parsedSel = {anchor: mapping.map(parsedSel.anchor), | ||
head: mapping.map(parsedSel.head)} } | ||
var tr = view.state.tr, handled = false, markChange | ||
var tr = view.state.tr, handled = false, markChange, $from1 | ||
if ($from.sameParent($to) && $from.parent.isTextblock && $from.pos != $to.pos) { | ||
@@ -257,8 +259,11 @@ if (change.endA == change.endB && | ||
{ tr.replace(from, to, parsed.slice(change.start - range.from, change.endB - range.from)) } | ||
if (parsedSel) | ||
{ tr.setSelection(Selection.between(tr.doc.resolve(parsedSel.anchor), | ||
tr.doc.resolve(parsedSel.head))) } | ||
if (parsedSel) { tr.setSelection(resolveSelection(tr.doc, mapping, parsedSel)) } | ||
view.props.onAction(tr.scrollAction()) | ||
} | ||
function resolveSelection(doc, mapping, parsedSel) { | ||
return Selection.between(doc.resolve(mapping.map(parsedSel.anchor)), | ||
doc.resolve(mapping.map(parsedSel.head))) | ||
} | ||
// : (Fragment, Fragment) → ?{mark: Mark, type: string} | ||
@@ -265,0 +270,0 @@ // Given two same-length, non-empty fragments of inline content, |
@@ -20,3 +20,4 @@ var ref = require("prosemirror-state"); | ||
var ref$5 = require("./decoration"); | ||
var viewDecorations = ref$5.viewDecorations;var assign; | ||
var viewDecorations = ref$5.viewDecorations; | ||
var Decoration = ref$5.Decoration;var assign; | ||
((assign = require("./decoration"), exports.Decoration = assign.Decoration, exports.DecorationSet = assign.DecorationSet)) | ||
@@ -27,3 +28,3 @@ | ||
// [props](#view.EditorProps). | ||
var EditorView = function EditorView(place, props) { | ||
var EditorView = function(place, props) { | ||
var this$1 = this; | ||
@@ -49,3 +50,3 @@ | ||
// :: dom.Node | ||
// :: dom.Element | ||
// The editable DOM node containing the document. (You probably | ||
@@ -55,9 +56,7 @@ // should not be directly interfering with its child nodes.) | ||
this.updateDOMForProps() | ||
if (place && place.appendChild) { place.appendChild(this.content) } | ||
else if (place) { place(this.content) } | ||
this.docView = docViewDesc(this.state.doc, viewDecorations(this), this.content, this) | ||
this.content.contentEditable = true | ||
this.editable = getEditable(this) | ||
this.docView = docViewDesc(this.state.doc, computeDocDeco(this), viewDecorations(this), this.content, this) | ||
@@ -67,2 +66,5 @@ this.lastSelectedViewDesc = null | ||
initInput(this) | ||
this.pluginViews = [] | ||
this.updatePluginViews() | ||
}; | ||
@@ -75,3 +77,3 @@ | ||
// the view's DOM. | ||
EditorView.prototype.update = function update (props) { | ||
EditorView.prototype.update = function (props) { | ||
this.props = props | ||
@@ -84,3 +86,3 @@ this.updateState(props.state) | ||
// other props. | ||
EditorView.prototype.updateState = function updateState (state) { | ||
EditorView.prototype.updateState = function (state) { | ||
var prev = this.state | ||
@@ -91,19 +93,18 @@ this.state = state | ||
var redrawn = false | ||
var decorations = viewDecorations(this) | ||
var prevEditable = this.editable | ||
this.editable = getEditable(this) | ||
var innerDeco = viewDecorations(this), outerDeco = computeDocDeco(this) | ||
if (!this.docView.matchesNode(state.doc, [], decorations)) { | ||
if (!this.docView.matchesNode(state.doc, outerDeco, innerDeco)) { | ||
stopObserving(this) | ||
this.docView.update(state.doc, [], decorations, this) | ||
this.docView.update(state.doc, outerDeco, innerDeco, this) | ||
startObserving(this) | ||
redrawn = true | ||
} | ||
if (redrawn || !state.selection.eq(prev.selection)) | ||
if (!state.selection.eq(prev.selection) || this.selectionReader.domChanged()) | ||
{ selectionToDOM(this, state.selection) } | ||
this.updateDOMForProps() | ||
if (prevEditable != this.editable) { this.selectionReader.editableChanged() } | ||
this.updatePluginViews(prev) | ||
// FIXME somehow schedule this relative to ui/update so that it | ||
// doesn't cause extra layout | ||
if (state.scrollToSelection > prev.scrollToSelection || prev.config != state.config) | ||
@@ -113,21 +114,23 @@ { scrollPosIntoView(this, state.selection.head == null ? state.selection.from : state.selection.from) } | ||
EditorView.prototype.updateDOMForProps = function updateDOMForProps () { | ||
EditorView.prototype.destroyPluginViews = function () { | ||
var view | ||
while (view = this.pluginViews.pop()) { if (view.destroy) { view.destroy() } } | ||
}; | ||
EditorView.prototype.updatePluginViews = function (prevState) { | ||
var this$1 = this; | ||
var spellcheck = !!this.someProp("spellcheck") | ||
if (spellcheck != this.content.spellcheck) { this.content.spellcheck = spellcheck } | ||
var label = this.someProp("label", function (f) { return f(this$1.state); }) || "" | ||
if (this.content.getAttribute("aria-label") != label) { this.content.setAttribute("aria-label", label) } | ||
var classes = ["ProseMirror", "ProseMirror-content"] // FIXME remove backwards-compat class | ||
if (this.focused) { classes.push("ProseMirror-focused") } | ||
if (this.state.selection.node) { classes.push("ProseMirror-nodeselection") } | ||
this.someProp("class", function (f) { | ||
var cls = f(this$1.state) | ||
if (!cls) { return } | ||
var array = cls.split(" ") | ||
for (var i = 0; i < array.length; i++) { classes.push(array[i]) } | ||
}) | ||
var className = classes.sort().join(" ") | ||
if (this.content.className != className) { this.content.className = className } | ||
var plugins = this.state.plugins | ||
if (!prevState || prevState.plugins != plugins) { | ||
this.destroyPluginViews() | ||
for (var i = 0; i < plugins.length; i++) { | ||
var plugin = plugins[i] | ||
if (plugin.options.view) { this$1.pluginViews.push(plugin.options.view(this$1)) } | ||
} | ||
} else { | ||
for (var i$1 = 0; i$1 < this.pluginViews.length; i$1++) { | ||
var pluginView = this$1.pluginViews[i$1] | ||
if (pluginView.update) { pluginView.update(this$1) } | ||
} | ||
} | ||
}; | ||
@@ -137,4 +140,4 @@ | ||
// Query whether the view has focus. | ||
EditorView.prototype.hasFocus = function hasFocus () { | ||
if (this.content.ownerDocument.activeElement != this.content) { return false } | ||
EditorView.prototype.hasFocus = function () { | ||
if (this.editable && this.content.ownerDocument.activeElement != this.content) { return false } | ||
var sel = this.root.getSelection() | ||
@@ -151,5 +154,5 @@ return sel.rangeCount && this.content.contains(sel.anchorNode.nodeType == 3 ? sel.anchorNode.parentNode : sel.anchorNode) | ||
// directly). | ||
EditorView.prototype.someProp = function someProp (propName, f) { | ||
EditorView.prototype.someProp = function (propName, f) { | ||
var prop = this.props && this.props[propName], value | ||
if (prop && (value = f ? f(prop) : prop)) { return value } | ||
if (prop != null && (value = f ? f(prop) : prop)) { return value } | ||
var plugins = this.state.plugins | ||
@@ -164,5 +167,5 @@ if (plugins) { for (var i = 0; i < plugins.length; i++) { | ||
// Focus the editor. | ||
EditorView.prototype.focus = function focus () { | ||
EditorView.prototype.focus = function () { | ||
selectionToDOM(this, this.state.selection, true) | ||
this.content.focus() | ||
if (this.editable) { this.content.focus() } | ||
}; | ||
@@ -193,3 +196,3 @@ | ||
// the top level. | ||
EditorView.prototype.posAtCoords = function posAtCoords$1 (coords) { return posAtCoords(this, coords) }; | ||
EditorView.prototype.posAtCoords = function (coords) { return posAtCoords(this, coords) }; | ||
@@ -200,3 +203,3 @@ // :: (number) → {left: number, right: number, top: number, bottom: number} | ||
// cursor-ish rectangle. | ||
EditorView.prototype.coordsAtPos = function coordsAtPos$1 (pos) { return coordsAtPos(this, pos) }; | ||
EditorView.prototype.coordsAtPos = function (pos) { return coordsAtPos(this, pos) }; | ||
@@ -210,3 +213,3 @@ // :: (union<"up", "down", "left", "right", "forward", "backward">, ?EditorState) → bool | ||
// isn't a cursor selection. | ||
EditorView.prototype.endOfTextblock = function endOfTextblock$1 (dir, state) { | ||
EditorView.prototype.endOfTextblock = function (dir, state) { | ||
return endOfTextblock(this, state || this.state, dir) | ||
@@ -218,4 +221,6 @@ }; | ||
// views](#view.NodeView). | ||
EditorView.prototype.destroy = function destroy () { | ||
EditorView.prototype.destroy = function () { | ||
this.destroyPluginViews() | ||
this.docView.destroy() | ||
this.selectionReader.destroy() | ||
EditorState.removeApplyListener(this.trackState) | ||
@@ -226,3 +231,3 @@ if (this.content.parentNode) { this.content.parentNode.removeChild(this.content) } | ||
// Used for testing. | ||
EditorView.prototype.dispatchEvent = function dispatchEvent$1 (event) { | ||
EditorView.prototype.dispatchEvent = function (event) { | ||
return dispatchEvent(this, event) | ||
@@ -234,2 +239,25 @@ }; | ||
function computeDocDeco(view) { | ||
var attrs = Object.create(null) | ||
attrs.class = "ProseMirror" + (view.focused ? " ProseMirror-focused" : "") + | ||
(view.state.selection.node ? " ProseMirror-nodeselection" : "") | ||
attrs.contenteditable = String(view.editable) | ||
view.someProp("attributes", function (value) { | ||
if (typeof value == "function") { value = value(view.state) } | ||
if (value) { for (var attr in value) { | ||
if (attr == "class") | ||
{ attrs.class += " " + value[attr] } | ||
else if (!attrs[attr] && attr != "contenteditable" && attr != "nodeName") | ||
{ attrs[attr] = String(value[attr]) } | ||
} } | ||
}) | ||
return [Decoration.node(0, view.state.doc.content.size, attrs)] | ||
} | ||
function getEditable(view) { | ||
return !view.someProp("editable", function (value) { return value(view.state) === false; }) | ||
} | ||
// EditorProps:: interface | ||
@@ -333,3 +361,3 @@ // | ||
// | ||
// nodeViews:: ?Object<(node: Node, view: EditorView, getPos: () → number) → NodeView> | ||
// nodeViews:: ?Object<(node: Node, view: EditorView, getPos: () → number, decorations: [Decoration]) → NodeView> | ||
// Allows you to pass custom rendering and behavior logic for nodes | ||
@@ -342,2 +370,8 @@ // and marks. Should map node and mark names to constructor function | ||
// | ||
// `decorations` is an array of node or inline decorations that are | ||
// active around the node. They are automatically drawn in the | ||
// normal way, and you will usually just want to ignore this, but | ||
// they can also be used as a way to provide context information to | ||
// the node view without adding it to the document itself. | ||
// | ||
// clipboardSerializer:: ?DOMSerializer | ||
@@ -353,15 +387,16 @@ // The DOM serializer to use when putting content onto the | ||
// | ||
// spellcheck:: ?bool | ||
// Controls whether the DOM spellcheck attribute is enabled on the | ||
// editable content. Defaults to false. | ||
// editable:: ?(EditorState) → bool | ||
// When this returns false, the content of the view is not directly | ||
// editable. | ||
// | ||
// class:: ?(state: EditorState) → ?string | ||
// Controls the CSS class name of the editor DOM node. Any classes | ||
// returned from this will be added to the default `ProseMirror` | ||
// class. | ||
// attributes:: ?union<Object<string>, (EditorState) → ?Object<string>> | ||
// Control the DOM attributes of the editable element. May be either | ||
// an object or a function going from an editor state to an object. | ||
// By default, the element will get a class `"ProseMirror"`, and | ||
// will have its `contentEditable` attribute determined by the | ||
// [`editable` prop](#view.EditorProps.editable). Additional classes | ||
// provided here will be added to the class. For other attributes, | ||
// the value provided first (as in | ||
// [`someProp`](#view.EditorView.someProp)) will be used. | ||
// | ||
// label:: ?(state: EditorState) → ?string | ||
// Can be used to set an `aria-label` attribute on the editable | ||
// content node. | ||
// | ||
// scrollThreshold:: ?number | ||
@@ -368,0 +403,0 @@ // Determines the distance (in pixels) between the cursor and the |
@@ -20,3 +20,3 @@ var ref = require("prosemirror-state"); | ||
// to invoke when the event fires. | ||
var handlers = {} | ||
var handlers = {}, editHandlers = {} | ||
@@ -35,3 +35,4 @@ function initInput(view) { | ||
view.content.addEventListener(event, function (event) { | ||
if (eventBelongsToView(view, event) && !view.someProp("handleDOMEvent", function (f) { return f(view, event); })) | ||
if ((view.editable || !(event.type in editHandlers)) && | ||
eventBelongsToView(view, event) && !view.someProp("handleDOMEvent", function (f) { return f(view, event); })) | ||
{ handler(view, event) } | ||
@@ -61,3 +62,3 @@ }) | ||
handlers.keydown = function (view, event) { | ||
editHandlers.keydown = function (view, event) { | ||
if (event.keyCode == 16) { view.shiftKey = true } | ||
@@ -71,7 +72,7 @@ if (view.inDOMChange) { return } | ||
handlers.keyup = function (view, e) { | ||
editHandlers.keyup = function (view, e) { | ||
if (e.keyCode == 16) { view.shiftKey = false } | ||
} | ||
handlers.keypress = function (view, event) { | ||
editHandlers.keypress = function (view, event) { | ||
if (view.inDOMChange || !event.charCode || | ||
@@ -235,3 +236,3 @@ event.ctrlKey && !event.altKey || browser.mac && event.metaKey) { return } | ||
var MouseDown = function MouseDown(view, pos, event, flushed) { | ||
var MouseDown = function(view, pos, event, flushed) { | ||
this.view = view | ||
@@ -268,3 +269,3 @@ this.pos = pos | ||
MouseDown.prototype.done = function done () { | ||
MouseDown.prototype.done = function () { | ||
this.view.root.removeEventListener("mouseup", this.up) | ||
@@ -279,3 +280,3 @@ this.view.root.removeEventListener("mousemove", this.move) | ||
MouseDown.prototype.up = function up (event) { | ||
MouseDown.prototype.up = function (event) { | ||
this.done() | ||
@@ -293,4 +294,3 @@ | ||
} else if (this.flushed) { | ||
this.view.focus() | ||
this.view.props.onAction(Selection.near(this.view.state.doc.resolve(this.pos.pos)).action({origin: "mouse"})) | ||
updateSelection(this.view, Selection.near(this.view.state.doc.resolve(this.pos.pos)), "mouse") | ||
event.preventDefault() | ||
@@ -302,3 +302,3 @@ } else { | ||
MouseDown.prototype.move = function move (event) { | ||
MouseDown.prototype.move = function (event) { | ||
if (!this.allowDefault && (Math.abs(this.x - event.clientX) > 4 || | ||
@@ -336,3 +336,3 @@ Math.abs(this.y - event.clientY) > 4)) | ||
handlers.compositionstart = handlers.compositionupdate = function (view) { | ||
editHandlers.compositionstart = editHandlers.compositionupdate = function (view) { | ||
DOMChange.start(view, true) | ||
@@ -342,3 +342,3 @@ if (view.state.storedMarks) { view.inDOMChange.finish(true) } | ||
handlers.compositionend = function (view, e) { | ||
editHandlers.compositionend = function (view, e) { | ||
if (!view.inDOMChange) { | ||
@@ -368,6 +368,5 @@ // We received a compositionend without having seen any previous | ||
function registerMutations(view, mutations) { | ||
for (var i = 0; i < mutations.length; i++) { | ||
var mut = mutations[i] | ||
if (mut.target == view.content && mut.type == "attributes") { continue } | ||
var desc = view.docView.nearestDesc(mut.target) | ||
if (view.editable) { for (var i = 0; i < mutations.length; i++) { | ||
var mut = mutations[i], desc = view.docView.nearestDesc(mut.target) | ||
if (desc == view.docView && mut.type == "attributes") { continue } | ||
if (!desc || desc.ignoreMutation(mut)) { continue } | ||
@@ -393,8 +392,8 @@ | ||
view.inDOMChange.addRange(from, to) | ||
} | ||
} } | ||
} | ||
handlers.input = function (view) { return DOMChange.start(view); } | ||
editHandlers.input = function (view) { return DOMChange.start(view); } | ||
handlers.copy = handlers.cut = function (view, e) { | ||
handlers.copy = editHandlers.cut = function (view, e) { | ||
var sel = view.state.selection, cut = e.type == "cut" | ||
@@ -415,3 +414,3 @@ if (sel.empty) { return } | ||
handlers.paste = function (view, e) { | ||
editHandlers.paste = function (view, e) { | ||
if (!e.clipboardData) { | ||
@@ -431,3 +430,3 @@ if (browser.ie && browser.ie_version <= 11) { DOMChange.start(view) } | ||
var Dragging = function Dragging(state, slice, range, move) { | ||
var Dragging = function(state, slice, range, move) { | ||
this.slice = slice | ||
@@ -473,5 +472,7 @@ this.range = range | ||
handlers.dragover = handlers.dragenter = function (_, e) { return e.preventDefault(); } | ||
editHandlers.dragover = editHandlers.dragenter = function (_, e) { return e.preventDefault(); } | ||
handlers.drop = function (view, e) { | ||
editHandlers.dragleave = function () { return null; } | ||
editHandlers.drop = function (view, e) { | ||
var dragging = view.dragging | ||
@@ -523,1 +524,4 @@ view.dragging = null | ||
} | ||
// Make sure all handlers get registered | ||
for (var prop in editHandlers) { handlers[prop] = editHandlers[prop] } |
@@ -10,3 +10,3 @@ var ref = require("prosemirror-state"); | ||
// as there is no DOM event for DOM selection changes. | ||
var SelectionReader = function SelectionReader(view) { | ||
var SelectionReader = function(view) { | ||
var this$1 = this; | ||
@@ -21,11 +21,20 @@ | ||
view.content.addEventListener("focus", function () { return this$1.poller.receivedFocus(); }) | ||
view.content.addEventListener("blur", function () { return this$1.poller.lostFocus(); }) | ||
view.content.addEventListener("focus", function () { return this$1.poller.start(); }) | ||
view.content.addEventListener("blur", function () { return this$1.poller.stop(); }) | ||
if (!view.editable) { this.poller.start() } | ||
}; | ||
SelectionReader.prototype.poll = function poll (origin) { this.poller.poll(origin) }; | ||
SelectionReader.prototype.destroy = function () { this.poller.stop() }; | ||
SelectionReader.prototype.poll = function (origin) { this.poller.poll(origin) }; | ||
SelectionReader.prototype.editableChanged = function () { | ||
if (!this.view.editable) { this.poller.start() } | ||
else if (!this.view.hasFocus()) { this.poller.stop() } | ||
}; | ||
// : () → bool | ||
// Whether the DOM selection has changed from the last known state. | ||
SelectionReader.prototype.domChanged = function domChanged () { | ||
SelectionReader.prototype.domChanged = function () { | ||
var sel = this.view.root.getSelection() | ||
@@ -37,3 +46,3 @@ return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || | ||
// Store the current state of the DOM selection. | ||
SelectionReader.prototype.storeDOMState = function storeDOMState (selection) { | ||
SelectionReader.prototype.storeDOMState = function (selection) { | ||
var sel = this.view.root.getSelection() | ||
@@ -48,3 +57,3 @@ this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset | ||
// current selection state to match. | ||
SelectionReader.prototype.readFromDOM = function readFromDOM (origin) { | ||
SelectionReader.prototype.readFromDOM = function (origin) { | ||
if (!this.view.hasFocus() || this.view.inDOMChange || !this.domChanged()) { return } | ||
@@ -75,72 +84,68 @@ | ||
function poller(reader) { | ||
// There's two polling models. On browsers that support the | ||
// selectionchange event (everything except Firefox, basically), we | ||
// register a listener for that whenever the editor is focused. | ||
if ("onselectionchange" in document) { return new (function () { | ||
function anonymous() { | ||
var this$1 = this; | ||
// There's two polling models. On browsers that support the | ||
// selectionchange event (everything except Firefox, basically), we | ||
// register a listener for that whenever the editor is focused. | ||
var SelectionChangePoller = function(reader) { | ||
var this$1 = this; | ||
this.listening = false | ||
this.curOrigin = null | ||
this.originTime = 0 | ||
this.listening = false | ||
this.curOrigin = null | ||
this.originTime = 0 | ||
this.readFunc = function () { return reader.readFromDOM(this$1.originTime > Date.now() - 50 ? this$1.curOrigin : null); } | ||
} | ||
this.readFunc = function () { return reader.readFromDOM(this$1.originTime > Date.now() - 50 ? this$1.curOrigin : null); } | ||
}; | ||
anonymous.prototype.poll = function poll (origin) { | ||
this.curOrigin = origin | ||
this.originTime = Date.now() | ||
}; | ||
SelectionChangePoller.prototype.poll = function (origin) { | ||
this.curOrigin = origin | ||
this.originTime = Date.now() | ||
}; | ||
anonymous.prototype.receivedFocus = function receivedFocus () { | ||
if (!this.listening) { | ||
document.addEventListener("selectionchange", this.readFunc) | ||
this.listening = true | ||
} | ||
}; | ||
SelectionChangePoller.prototype.start = function () { | ||
if (!this.listening) { | ||
document.addEventListener("selectionchange", this.readFunc) | ||
this.listening = true | ||
} | ||
}; | ||
anonymous.prototype.lostFocus = function lostFocus () { | ||
if (this.listening) { | ||
document.removeEventListener("selectionchange", this.readFunc) | ||
this.listening = false | ||
} | ||
}; | ||
SelectionChangePoller.prototype.stop = function () { | ||
if (this.listening) { | ||
document.removeEventListener("selectionchange", this.readFunc) | ||
this.listening = false | ||
} | ||
}; | ||
return anonymous; | ||
}()) } | ||
// On Firefox, we use timeout-based polling. | ||
return new (function () { | ||
function anonymous$1() { | ||
// The timeout ID for the poller when active. | ||
this.polling = null | ||
this.reader = reader | ||
this.pollFunc = this.doPoll.bind(this, null) | ||
} | ||
// On Firefox, we use timeout-based polling. | ||
var TimeoutPoller = function(reader) { | ||
// The timeout ID for the poller when active. | ||
this.polling = null | ||
this.reader = reader | ||
this.pollFunc = this.doPoll.bind(this, null) | ||
}; | ||
anonymous$1.prototype.doPoll = function doPoll (origin) { | ||
if (this.reader.view.hasFocus()) { | ||
this.reader.readFromDOM(origin) | ||
this.polling = setTimeout(this.pollFunc, 100) | ||
} else { | ||
this.polling = null | ||
} | ||
}; | ||
TimeoutPoller.prototype.doPoll = function (origin) { | ||
var view = this.reader.view | ||
if (view.focused || !view.editable) { | ||
this.reader.readFromDOM(origin) | ||
this.polling = setTimeout(this.pollFunc, 100) | ||
} else { | ||
this.polling = null | ||
} | ||
}; | ||
anonymous$1.prototype.poll = function poll (origin) { | ||
clearTimeout(this.polling) | ||
this.polling = setTimeout(origin ? this.doPoll.bind(this, origin) : this.pollFunc, 0) | ||
}; | ||
TimeoutPoller.prototype.poll = function (origin) { | ||
clearTimeout(this.polling) | ||
this.polling = setTimeout(origin ? this.doPoll.bind(this, origin) : this.pollFunc, 0) | ||
}; | ||
anonymous$1.prototype.receivedFocus = function receivedFocus () { | ||
if (this.polling == null) { this.poll() } | ||
}; | ||
TimeoutPoller.prototype.start = function () { | ||
if (this.polling == null) { this.poll() } | ||
}; | ||
anonymous$1.prototype.lostFocus = function lostFocus () { | ||
clearTimeout(this.polling) | ||
this.polling = null | ||
}; | ||
TimeoutPoller.prototype.stop = function () { | ||
clearTimeout(this.polling) | ||
this.polling = null | ||
}; | ||
return anonymous$1; | ||
}()) | ||
function poller(reader) { | ||
return new ("onselectionchange" in document ? SelectionChangePoller : TimeoutPoller)(reader) | ||
} | ||
@@ -154,3 +159,3 @@ | ||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=921444 | ||
else if (browser.gecko) { view.content.focus() } | ||
else if (browser.gecko && view.editable) { view.content.focus() } | ||
} | ||
@@ -157,0 +162,0 @@ |
var ref = require("prosemirror-transform"); | ||
var Mapping = ref.Mapping; | ||
var TrackedRecord = function TrackedRecord(prev, mapping, state) { | ||
var TrackedRecord = function(prev, mapping, state) { | ||
this.prev = prev | ||
@@ -10,7 +10,7 @@ this.mapping = mapping | ||
var TrackMappings = function TrackMappings(state) { | ||
var TrackMappings = function(state) { | ||
this.seen = [new TrackedRecord(null, null, state)] | ||
}; | ||
TrackMappings.prototype.find = function find (state) { | ||
TrackMappings.prototype.find = function (state) { | ||
var this$1 = this; | ||
@@ -24,3 +24,3 @@ | ||
TrackMappings.prototype.track = function track (old, action, state) { | ||
TrackMappings.prototype.track = function (old, action, state) { | ||
var found = this.seen.length < 200 ? this.find(old) : null | ||
@@ -31,3 +31,3 @@ if (found) | ||
TrackMappings.prototype.getMapping = function getMapping (state) { | ||
TrackMappings.prototype.getMapping = function (state) { | ||
var found = this.find(state) | ||
@@ -34,0 +34,0 @@ if (!found) { return null } |
@@ -27,10 +27,11 @@ var ref = require("prosemirror-model"); | ||
// | ||
// update:: ?(node: Node, deco: DecorationSet) → bool | ||
// update:: ?(node: Node, decorations: [Decoration]) → bool | ||
// When given, this will be called when the view is updating itself. | ||
// It will be given a node (possibly of a different type), and a | ||
// decoration set (which it may ignore, if it chooses not to support | ||
// decorations), and should return true if it was able to update to | ||
// that node, and false otherwise. If the node view has a | ||
// `contentDOM` property (or no `dom` property), updating its child | ||
// nodes will be handled by ProseMirror. | ||
// It will be given a node (possibly of a different type), and an | ||
// array of active decorations (which are automatically drawn, and | ||
// the node view may ignore if it isn't interested in them), and | ||
// should return true if it was able to update to that node, and | ||
// false otherwise. If the node view has a `contentDOM` property (or | ||
// no `dom` property), updating its child nodes will be handled by | ||
// ProseMirror. | ||
// | ||
@@ -83,3 +84,3 @@ // selectNode:: ?() | ||
// basic structure and shared methods. | ||
var ViewDesc = function ViewDesc(parent, children, dom, contentDOM) { | ||
var ViewDesc = function(parent, children, dom, contentDOM) { | ||
this.parent = parent | ||
@@ -101,6 +102,6 @@ this.children = children | ||
// widget/mark/node. | ||
ViewDesc.prototype.matchesWidget = function matchesWidget () { return false }; | ||
ViewDesc.prototype.matchesMark = function matchesMark () { return false }; | ||
ViewDesc.prototype.matchesNode = function matchesNode () { return false }; | ||
ViewDesc.prototype.matchesHack = function matchesHack () { return false }; | ||
ViewDesc.prototype.matchesWidget = function () { return false }; | ||
ViewDesc.prototype.matchesMark = function () { return false }; | ||
ViewDesc.prototype.matchesNode = function () { return false }; | ||
ViewDesc.prototype.matchesHack = function () { return false }; | ||
@@ -111,3 +112,3 @@ // : () → ?ParseRule | ||
// parse them. | ||
ViewDesc.prototype.parseRule = function parseRule () { return null }; | ||
ViewDesc.prototype.parseRule = function () { return null }; | ||
@@ -117,3 +118,3 @@ // : (dom.Event) → bool | ||
// from certain descs. | ||
ViewDesc.prototype.stopEvent = function stopEvent () { return false }; | ||
ViewDesc.prototype.stopEvent = function () { return false }; | ||
@@ -133,3 +134,3 @@ // The size of the content represented by this desc. | ||
ViewDesc.prototype.destroy = function destroy () { | ||
ViewDesc.prototype.destroy = function () { | ||
var this$1 = this; | ||
@@ -142,3 +143,3 @@ | ||
ViewDesc.prototype.posBeforeChild = function posBeforeChild (child) { | ||
ViewDesc.prototype.posBeforeChild = function (child) { | ||
var this$1 = this; | ||
@@ -162,3 +163,3 @@ | ||
// : (dom.Node, number, ?number) → number | ||
ViewDesc.prototype.localPosFromDOM = function localPosFromDOM (dom, offset, bias) { | ||
ViewDesc.prototype.localPosFromDOM = function (dom, offset, bias) { | ||
var this$1 = this; | ||
@@ -212,12 +213,15 @@ | ||
// this one. | ||
ViewDesc.prototype.nearestDesc = function nearestDesc (dom) { | ||
ViewDesc.prototype.nearestDesc = function (dom) { | ||
var this$1 = this; | ||
for (; dom; dom = dom.parentNode) { | ||
var desc = this$1.getDesc(dom) | ||
if (desc) { return desc } | ||
for (var first = true, cur = dom; cur; cur = cur.parentNode) { | ||
var desc = this$1.getDesc(cur) | ||
if (desc) { | ||
if (first && desc.nodeDOM && !desc.nodeDOM.contains(dom)) { first = false } | ||
else { return desc } | ||
} | ||
} | ||
}; | ||
ViewDesc.prototype.getDesc = function getDesc (dom) { | ||
ViewDesc.prototype.getDesc = function (dom) { | ||
var this$1 = this; | ||
@@ -229,3 +233,3 @@ | ||
ViewDesc.prototype.posFromDOM = function posFromDOM (dom, offset, bias) { | ||
ViewDesc.prototype.posFromDOM = function (dom, offset, bias) { | ||
var this$1 = this; | ||
@@ -242,3 +246,3 @@ | ||
// parent node overrode rendering, there might not be one.) | ||
ViewDesc.prototype.descAt = function descAt (pos) { | ||
ViewDesc.prototype.descAt = function (pos) { | ||
var this$1 = this; | ||
@@ -258,3 +262,3 @@ | ||
// : (number, ?bool) → {node: dom.Node, offset: number} | ||
ViewDesc.prototype.domFromPos = function domFromPos (pos, searchDOM) { | ||
ViewDesc.prototype.domFromPos = function (pos, searchDOM) { | ||
var this$1 = this; | ||
@@ -277,3 +281,3 @@ | ||
// the offset that corresponds to a given child. | ||
ViewDesc.prototype.findDOMOffset = function findDOMOffset (i, searchDOM) { | ||
ViewDesc.prototype.findDOMOffset = function (i, searchDOM) { | ||
var this$1 = this; | ||
@@ -284,3 +288,5 @@ | ||
for (var j = i - 1; j >= 0; j--) { | ||
var found = Array.prototype.indexOf.call(content.childNodes, this$1.children[j].dom) | ||
var child = this$1.children[j] | ||
if (!child.size) { continue } | ||
var found = Array.prototype.indexOf.call(content.childNodes, child.dom) | ||
if (found > -1) { return found + 1 } | ||
@@ -291,3 +297,5 @@ } | ||
for (var j$1 = i; j$1 < this.children.length; j$1++) { | ||
var found$1 = Array.prototype.indexOf.call(content.childNodes, this$1.children[j$1].dom) | ||
var child$1 = this$1.children[j$1] | ||
if (!child$1.size) { continue } | ||
var found$1 = Array.prototype.indexOf.call(content.childNodes, child$1.dom) | ||
if (found$1 > -1) { return found$1 } | ||
@@ -300,3 +308,3 @@ } | ||
// : (number) → dom.Node | ||
ViewDesc.prototype.domAfterPos = function domAfterPos (pos) { | ||
ViewDesc.prototype.domAfterPos = function (pos) { | ||
var ref = this.domFromPos(pos); | ||
@@ -316,3 +324,3 @@ var node = ref.node; | ||
// case we just use whatever domFromPos produces as a best effort. | ||
ViewDesc.prototype.setSelection = function setSelection (anchor, head, root) { | ||
ViewDesc.prototype.setSelection = function (anchor, head, root) { | ||
var this$1 = this; | ||
@@ -350,3 +358,3 @@ | ||
// : (dom.MutationRecord) → bool | ||
ViewDesc.prototype.ignoreMutation = function ignoreMutation (_mutation) { | ||
ViewDesc.prototype.ignoreMutation = function (_mutation) { | ||
return !this.contentDOM | ||
@@ -357,3 +365,3 @@ }; | ||
// by a DOM change, so that the next update will redraw it. | ||
ViewDesc.prototype.markDirty = function markDirty (from, to) { | ||
ViewDesc.prototype.markDirty = function (from, to) { | ||
var this$1 = this; | ||
@@ -363,3 +371,3 @@ | ||
var child = this$1.children[i], end = offset + child.size | ||
if (from < end && to > offset) { | ||
if (offset == end ? from <= end && to >= offset : from < end && to > offset) { | ||
var startInside = offset + child.border, endInside = end - child.border | ||
@@ -397,7 +405,7 @@ if (from >= startInside && to <= endInside) { | ||
WidgetViewDesc.prototype.matchesWidget = function matchesWidget (widget) { return this.dirty == NOT_DIRTY && widget.type == this.widget.type }; | ||
WidgetViewDesc.prototype.matchesWidget = function (widget) { return this.dirty == NOT_DIRTY && widget.type == this.widget.type }; | ||
WidgetViewDesc.prototype.parseRule = function parseRule () { return {ignore: true} }; | ||
WidgetViewDesc.prototype.parseRule = function () { return {ignore: true} }; | ||
WidgetViewDesc.prototype.stopEvent = function stopEvent (event) { | ||
WidgetViewDesc.prototype.stopEvent = function (event) { | ||
var stop = this.widget.type.options.stopEvent | ||
@@ -425,3 +433,3 @@ return stop ? stop(event) : false | ||
MarkViewDesc.create = function create (parent, mark, view) { | ||
MarkViewDesc.create = function (parent, mark, view) { | ||
var custom = customNodeViews(view)[mark.type.name] | ||
@@ -433,5 +441,5 @@ var spec = custom && custom(mark, view) | ||
MarkViewDesc.prototype.parseRule = function parseRule () { return {mark: this.mark.type.name, attrs: this.mark.attrs, contentElement: this.contentDOM} }; | ||
MarkViewDesc.prototype.parseRule = function () { return {mark: this.mark.type.name, attrs: this.mark.attrs, contentElement: this.contentDOM} }; | ||
MarkViewDesc.prototype.matchesMark = function matchesMark (mark) { return this.dirty != NODE_DIRTY && this.mark.eq(mark) }; | ||
MarkViewDesc.prototype.matchesMark = function (mark) { return this.dirty != NODE_DIRTY && this.mark.eq(mark) }; | ||
@@ -445,4 +453,5 @@ return MarkViewDesc; | ||
var NodeViewDesc = (function (ViewDesc) { | ||
function NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, view) { | ||
function NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view) { | ||
ViewDesc.call(this, parent, node.isLeaf ? nothing : [], dom, contentDOM) | ||
this.nodeDOM = nodeDOM | ||
this.node = node | ||
@@ -469,3 +478,3 @@ this.outerDeco = outerDeco | ||
// never need.) | ||
NodeViewDesc.create = function create (parent, node, outerDeco, innerDeco, view) { | ||
NodeViewDesc.create = function (parent, node, outerDeco, innerDeco, view) { | ||
var custom = customNodeViews(view)[node.type.name], descObj | ||
@@ -475,5 +484,4 @@ var spec = custom && custom(node, view, function () { | ||
// own position) | ||
if (!descObj) { return parent.posAtStart + parent.size } | ||
if (descObj.parent) { return descObj.parent.posBeforeChild(descObj) } | ||
}) | ||
if (descObj && descObj.parent) { return descObj.parent.posBeforeChild(descObj) } | ||
}, outerDeco) | ||
@@ -483,17 +491,18 @@ var dom = spec && spec.dom, contentDOM = spec && spec.contentDOM | ||
((assign = DOMSerializer.renderSpec(document, node.type.spec.toDOM(node)), dom = assign.dom, contentDOM = assign.contentDOM)) } | ||
var startDOM = dom | ||
for (var i = 0; i < outerDeco.length; i++) | ||
{ dom = applyOuterDeco(dom, outerDeco[i].type.attrs, node) } | ||
if (!contentDOM && !node.isText) { dom.contentEditable = false } | ||
var nodeDOM = dom | ||
dom = applyOuterDeco(dom, outerDeco, node, !!contentDOM) | ||
if (spec) | ||
{ return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, spec, view) } | ||
{ return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, spec, view) } | ||
else if (node.isText) | ||
{ return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, startDOM, view) } | ||
{ return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM, view) } | ||
else | ||
{ return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, view) } | ||
{ return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view) } | ||
}; | ||
NodeViewDesc.prototype.parseRule = function parseRule () { return {node: this.node.type.name, attrs: this.node.attrs, contentElement: this.contentDOM} }; | ||
NodeViewDesc.prototype.parseRule = function () { return {node: this.node.type.name, attrs: this.node.attrs, contentElement: this.contentDOM} }; | ||
NodeViewDesc.prototype.matchesNode = function matchesNode (node, outerDeco, innerDeco) { | ||
NodeViewDesc.prototype.matchesNode = function (node, outerDeco, innerDeco) { | ||
return this.dirty == NOT_DIRTY && node.eq(this.node) && | ||
@@ -511,3 +520,5 @@ sameOuterDeco(outerDeco, this.outerDeco) && innerDeco.eq(this.innerDeco) | ||
// `this.children`. | ||
NodeViewDesc.prototype.updateChildren = function updateChildren (view) { | ||
NodeViewDesc.prototype.updateChildren = function (view) { | ||
var this$1 = this; | ||
var updater = new ViewTreeUpdater(this) | ||
@@ -518,3 +529,3 @@ iterDeco(this.node, this.innerDeco, function (widget) { | ||
updater.placeWidget(widget) | ||
}, function (child, outerDeco, innerDeco) { | ||
}, function (child, outerDeco, innerDeco, i) { | ||
// Make sure the wrapping mark descs match the node's marks. | ||
@@ -526,3 +537,3 @@ updater.syncToMarks(child.marks, view) | ||
// Or try updating the next desc to reflect this node. | ||
updater.updateNode(child, outerDeco, innerDeco, view) || | ||
updater.updateNextNode(child, outerDeco, innerDeco, view, this$1.node.content, i) || | ||
// Or just add it as a new desc. | ||
@@ -540,3 +551,3 @@ updater.addNode(child, outerDeco, innerDeco, view) | ||
NodeViewDesc.prototype.renderChildren = function renderChildren () { | ||
NodeViewDesc.prototype.renderChildren = function () { | ||
renderDescs(this.contentDOM, this.children, NodeViewDesc.is) | ||
@@ -549,6 +560,6 @@ if (browser.ios) { iosHacks(this.dom) } | ||
// do so and return true. | ||
NodeViewDesc.prototype.update = function update (node, outerDeco, innerDeco, view) { | ||
NodeViewDesc.prototype.update = function (node, outerDeco, innerDeco, view) { | ||
if (this.dirty == NODE_DIRTY || | ||
!node.sameMarkup(this.node) || | ||
!sameOuterDeco(outerDeco, this.outerDeco)) { return false } | ||
!node.sameMarkup(this.node)) { return false } | ||
this.updateOuterDeco(outerDeco) | ||
this.node = node | ||
@@ -561,10 +572,19 @@ this.innerDeco = innerDeco | ||
NodeViewDesc.prototype.updateOuterDeco = function (outerDeco) { | ||
if (sameOuterDeco(outerDeco, this.outerDeco)) { return } | ||
var content = !!this.contentDOM, needsWrap = this.nodeDOM.nodeType != 1 | ||
this.dom = patchOuterDeco(this.dom, this.nodeDOM, | ||
computeOuterDeco(this.outerDeco, this.node, content, needsWrap), | ||
computeOuterDeco(outerDeco, this.node, content, needsWrap)) | ||
this.outerDeco = outerDeco | ||
}; | ||
// Mark this node as being the selected node. | ||
NodeViewDesc.prototype.selectNode = function selectNode () { | ||
this.dom.classList.add("ProseMirror-selectednode") | ||
NodeViewDesc.prototype.selectNode = function () { | ||
this.nodeDOM.classList.add("ProseMirror-selectednode") | ||
}; | ||
// Remove selected node marking from this node. | ||
NodeViewDesc.prototype.deselectNode = function deselectNode () { | ||
this.dom.classList.remove("ProseMirror-selectednode") | ||
NodeViewDesc.prototype.deselectNode = function () { | ||
this.nodeDOM.classList.remove("ProseMirror-selectednode") | ||
}; | ||
@@ -579,4 +599,5 @@ | ||
// and used by the view class. | ||
function docViewDesc(doc, deco, dom, view) { | ||
return new NodeViewDesc(null, doc, nothing, deco, dom, dom, view) | ||
function docViewDesc(doc, outerDeco, innerDeco, dom, view) { | ||
applyOuterDeco(dom, outerDeco, doc, true) | ||
return new NodeViewDesc(null, doc, outerDeco, innerDeco, dom, dom, dom, view) | ||
} | ||
@@ -586,5 +607,4 @@ exports.docViewDesc = docViewDesc | ||
var TextViewDesc = (function (NodeViewDesc) { | ||
function TextViewDesc(parent, node, outerDeco, innerDeco, dom, textDOM, view) { | ||
NodeViewDesc.call(this, parent, node, outerDeco, innerDeco, dom, null, view) | ||
this.textDOM = textDOM | ||
function TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM, view) { | ||
NodeViewDesc.call(this, parent, node, outerDeco, innerDeco, dom, null, nodeDOM, view) | ||
} | ||
@@ -596,12 +616,12 @@ | ||
TextViewDesc.prototype.parseRule = function parseRule () { | ||
return {skip: this.textDOM.parentNode} | ||
TextViewDesc.prototype.parseRule = function () { | ||
return {skip: this.nodeDOM.parentNode} | ||
}; | ||
TextViewDesc.prototype.update = function update (node, outerDeco) { | ||
TextViewDesc.prototype.update = function (node, outerDeco) { | ||
if (this.dirty == NODE_DIRTY || (this.dirty != NOT_DIRTY && !this.inParent) || | ||
!node.sameMarkup(this.node) || | ||
!sameOuterDeco(outerDeco, this.outerDeco)) { return false } | ||
if ((this.dirty != NOT_DIRTY || node.text != this.node.text) && node.text != this.textDOM.nodeValue) | ||
{ this.textDOM.nodeValue = node.text } | ||
!node.sameMarkup(this.node)) { return false } | ||
this.updateOuterDeco(outerDeco) | ||
if ((this.dirty != NOT_DIRTY || node.text != this.node.text) && node.text != this.nodeDOM.nodeValue) | ||
{ this.nodeDOM.nodeValue = node.text } | ||
this.node = node | ||
@@ -612,18 +632,18 @@ this.dirty = NOT_DIRTY | ||
TextViewDesc.prototype.inParent = function inParent () { | ||
TextViewDesc.prototype.inParent = function () { | ||
var parentDOM = this.parent.contentDOM | ||
for (var n = this.textDOM; n; n = n.parentNode) { if (n == parentDOM) { return true } } | ||
for (var n = this.nodeDOM; n; n = n.parentNode) { if (n == parentDOM) { return true } } | ||
return false | ||
}; | ||
TextViewDesc.prototype.domFromPos = function domFromPos (pos, searchDOM) { | ||
return {node: this.textDOM, offset: searchDOM ? Math.max(pos, this.textDOM.nodeValue.length) : pos} | ||
TextViewDesc.prototype.domFromPos = function (pos, searchDOM) { | ||
return {node: this.nodeDOM, offset: searchDOM ? Math.max(pos, this.nodeDOM.nodeValue.length) : pos} | ||
}; | ||
TextViewDesc.prototype.localPosFromDOM = function localPosFromDOM (dom, offset, bias) { | ||
if (dom == this.textDOM) { return this.posAtStart + Math.min(offset, this.node.text.length) } | ||
TextViewDesc.prototype.localPosFromDOM = function (dom, offset, bias) { | ||
if (dom == this.nodeDOM) { return this.posAtStart + Math.min(offset, this.node.text.length) } | ||
return NodeViewDesc.prototype.localPosFromDOM.call(this, dom, offset, bias) | ||
}; | ||
TextViewDesc.prototype.ignoreMutation = function ignoreMutation (mutation) { | ||
TextViewDesc.prototype.ignoreMutation = function (mutation) { | ||
return mutation.type != "characterData" | ||
@@ -637,15 +657,15 @@ }; | ||
// around contentEditable terribleness. | ||
var HackViewDesc = (function (ViewDesc) { | ||
function HackViewDesc(parent, type, dom) { | ||
ViewDesc.call(this, parent, nothing, dom, null) | ||
this.type = type | ||
var BRHackViewDesc = (function (ViewDesc) { | ||
function BRHackViewDesc () { | ||
ViewDesc.apply(this, arguments); | ||
} | ||
if ( ViewDesc ) HackViewDesc.__proto__ = ViewDesc; | ||
HackViewDesc.prototype = Object.create( ViewDesc && ViewDesc.prototype ); | ||
HackViewDesc.prototype.constructor = HackViewDesc; | ||
HackViewDesc.prototype.parseRule = function parseRule () { return {ignore: true} }; | ||
HackViewDesc.prototype.matchesHack = function matchesHack (type) { return this.dirty == NOT_DIRTY && this.type == type }; | ||
if ( ViewDesc ) BRHackViewDesc.__proto__ = ViewDesc; | ||
BRHackViewDesc.prototype = Object.create( ViewDesc && ViewDesc.prototype ); | ||
BRHackViewDesc.prototype.constructor = BRHackViewDesc; | ||
return HackViewDesc; | ||
BRHackViewDesc.prototype.parseRule = function () { return {ignore: true} }; | ||
BRHackViewDesc.prototype.matchesHack = function () { return this.dirty == NOT_DIRTY }; | ||
return BRHackViewDesc; | ||
}(ViewDesc)); | ||
@@ -657,4 +677,4 @@ | ||
var CustomNodeViewDesc = (function (NodeViewDesc) { | ||
function CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, spec, view) { | ||
NodeViewDesc.call(this, parent, node, outerDeco, innerDeco, dom, contentDOM, view) | ||
function CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, spec, view) { | ||
NodeViewDesc.call(this, parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view) | ||
this.spec = spec | ||
@@ -670,5 +690,5 @@ } | ||
// updates the children. | ||
CustomNodeViewDesc.prototype.update = function update (node, outerDeco, innerDeco, view) { | ||
CustomNodeViewDesc.prototype.update = function (node, outerDeco, innerDeco, view) { | ||
if (this.spec.update) { | ||
var result = this.spec.update(node, innerDeco) | ||
var result = this.spec.update(node, outerDeco) | ||
if (result) { | ||
@@ -679,20 +699,22 @@ this.node = node | ||
return result | ||
} else if (!this.contentDOM && !node.isLeaf) { | ||
return false | ||
} else { | ||
return NodeViewDesc.prototype.update.call(this, node, outerDeco, innerDeco, view) | ||
return NodeViewDesc.prototype.update.call(this, node, outerDeco, this.contentDOM ? this.innerDeco : innerDeco, view) | ||
} | ||
}; | ||
CustomNodeViewDesc.prototype.selectNode = function selectNode () { | ||
CustomNodeViewDesc.prototype.selectNode = function () { | ||
this.spec.selectNode ? this.spec.selectNode() : NodeViewDesc.prototype.selectNode.call(this) | ||
}; | ||
CustomNodeViewDesc.prototype.deselectNode = function deselectNode () { | ||
CustomNodeViewDesc.prototype.deselectNode = function () { | ||
this.spec.deselectNode ? this.spec.deselectNode() : NodeViewDesc.prototype.deselectNode.call(this) | ||
}; | ||
CustomNodeViewDesc.prototype.setSelection = function setSelection (anchor, head, root) { | ||
CustomNodeViewDesc.prototype.setSelection = function (anchor, head, root) { | ||
this.spec.setSelection ? this.spec.setSelection(anchor, head, root) : NodeViewDesc.prototype.setSelection.call(this, anchor, head, root) | ||
}; | ||
CustomNodeViewDesc.prototype.destroy = function destroy () { | ||
CustomNodeViewDesc.prototype.destroy = function () { | ||
if (this.spec.destroy) { this.spec.destroy() } | ||
@@ -702,7 +724,7 @@ NodeViewDesc.prototype.destroy.call(this) | ||
CustomNodeViewDesc.prototype.stopEvent = function stopEvent (event) { | ||
CustomNodeViewDesc.prototype.stopEvent = function (event) { | ||
return this.spec.stopEvent ? this.spec.stopEvent(event) : false | ||
}; | ||
CustomNodeViewDesc.prototype.ignoreMutation = function ignoreMutation (mutation) { | ||
CustomNodeViewDesc.prototype.ignoreMutation = function (mutation) { | ||
return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : NodeViewDesc.prototype.ignoreMutation.call(this, mutation) | ||
@@ -734,21 +756,89 @@ }; | ||
// : (dom.Node, Object, Node) → dom.Node | ||
// Apply the extra attributes and potentially add a wrapper for a | ||
// decoration. | ||
function applyOuterDeco(dom, attrs, node) { | ||
if (attrs.nodeName || dom.nodeType != 1) { | ||
var wrap = document.createElement(attrs.nodeName || (node.isInline ? "span" : "div")) | ||
wrap.appendChild(dom) | ||
dom = wrap | ||
var OuterDecoLevel = function(nodeName) { | ||
if (nodeName) { this.nodeName = nodeName } | ||
}; | ||
OuterDecoLevel.prototype = Object.create(null) | ||
var noDeco = [new OuterDecoLevel] | ||
function computeOuterDeco(outerDeco, node, hasContent, needsWrap) { | ||
if (hasContent && outerDeco.length == 0) { return noDeco } | ||
var top = needsWrap ? noDeco[0] : new OuterDecoLevel, result = [top] | ||
if (outerDeco.length) { | ||
for (var i = 0; i < outerDeco.length; i++) { | ||
var attrs = outerDeco[i].type.attrs, cur = top | ||
if (!attrs) { continue } | ||
if (attrs.nodeName) | ||
{ result.push(cur = new OuterDecoLevel(attrs.nodeName)) } | ||
for (var name in attrs) { | ||
var val = attrs[name] | ||
if (val == null) { continue } | ||
if (needsWrap && result.length == 1) | ||
{ result.push(cur = top = new OuterDecoLevel(node.isInline ? "span" : "div")) } | ||
if (name == "class") { cur.class = (cur.class ? cur.class + " " : "") + val } | ||
else if (name == "style") { cur.style = (cur.style ? cur.style + ";" : "") + val } | ||
else if (name != "nodeName") { cur[name] = val } | ||
} | ||
} | ||
} | ||
for (var name in attrs) { | ||
var val = attrs[name] | ||
if (name == "class") { (ref = dom.classList).add.apply(ref, val.split(" ")) } | ||
else if (name == "style") { dom.style.cssText += ";" + val } | ||
else if (name != "nodeName") { dom.setAttribute(name, val) } | ||
if (!hasContent && node.isBlock && result.length == 1 && !result[0].nodeName) | ||
{ result.push(new OuterDecoLevel("div")) } | ||
return result | ||
} | ||
function patchOuterDeco(outerDOM, nodeDOM, prevComputed, curComputed) { | ||
// Shortcut for trivial case | ||
if (prevComputed == noDeco && curComputed == noDeco) { return nodeDOM } | ||
var curDOM = nodeDOM | ||
for (var i = 0; i < curComputed.length; i++) { | ||
var deco = curComputed[i], prev = prevComputed[i] | ||
if (i) { | ||
var parent = (void 0) | ||
if (prev && prev.nodeName == deco.nodeName && curDOM != outerDOM && | ||
(parent = nodeDOM.parentNode) && parent.tagName.toLowerCase() == deco.nodeName) { | ||
curDOM = parent | ||
} else { | ||
parent = document.createElement(deco.nodeName) | ||
parent.appendChild(curDOM) | ||
curDOM = parent | ||
} | ||
} | ||
patchAttributes(curDOM, prev || noDeco[0], deco) | ||
} | ||
return dom | ||
var ref; | ||
return curDOM | ||
} | ||
function patchAttributes(dom, prev, cur) { | ||
for (var name in prev) | ||
{ if (name != "class" && name != "style" && name != "nodeName" && !(name in cur)) | ||
{ dom.removeAttribute(name) } } | ||
for (var name$1 in cur) | ||
{ if (name$1 != "class" && name$1 != "style" && name$1 != "nodeName" && cur[name$1] != prev[name$1]) | ||
{ dom.setAttribute(name$1, cur[name$1]) } } | ||
if (prev.class != cur.class) { | ||
var prevList = prev.class ? prev.class.split(" ") : nothing | ||
var curList = cur.class ? cur.class.split(" ") : nothing | ||
for (var i = 0; i < prevList.length; i++) { if (curList.indexOf(prevList[i]) == -1) | ||
{ dom.classList.remove(prevList[i]) } } | ||
for (var i$1 = 0; i$1 < curList.length; i$1++) { if (prevList.indexOf(curList[i$1]) == -1) | ||
{ dom.classList.add(curList[i$1]) } } | ||
} | ||
if (prev.style != cur.style) { | ||
var text = dom.style.cssText, found | ||
if (prev.style && (found = text.index(prev.style)) > -1) | ||
{ text = text.slice(0, found) + text.slice(found + prev.style.length) } | ||
dom.style.cssText = text + (cur.style || "") | ||
} | ||
} | ||
function applyOuterDeco(dom, deco, node, hasContent) { | ||
return patchOuterDeco(dom, dom, noDeco, computeOuterDeco(deco, node, hasContent, dom.nodeType != 1)) | ||
} | ||
// : ([Decoration], [Decoration]) → bool | ||
@@ -770,3 +860,3 @@ function sameOuterDeco(a, b) { | ||
// the widget and node descs inside of them. | ||
var ViewTreeUpdater = function ViewTreeUpdater(top) { | ||
var ViewTreeUpdater = function(top) { | ||
this.top = top | ||
@@ -785,3 +875,3 @@ // Index into `this.top`'s child array, represents the current | ||
// `this.top`. | ||
ViewTreeUpdater.prototype.destroyBetween = function destroyBetween (start, end) { | ||
ViewTreeUpdater.prototype.destroyBetween = function (start, end) { | ||
var this$1 = this; | ||
@@ -796,3 +886,3 @@ | ||
// Destroy all remaining children in `this.top`. | ||
ViewTreeUpdater.prototype.destroyRest = function destroyRest () { | ||
ViewTreeUpdater.prototype.destroyRest = function () { | ||
this.destroyBetween(this.index, this.top.children.length) | ||
@@ -804,3 +894,3 @@ }; | ||
// marks, reusing existing mark descs when possible. | ||
ViewTreeUpdater.prototype.syncToMarks = function syncToMarks (marks, view) { | ||
ViewTreeUpdater.prototype.syncToMarks = function (marks, view) { | ||
var this$1 = this; | ||
@@ -840,6 +930,5 @@ | ||
// return true when successful. | ||
ViewTreeUpdater.prototype.findNodeMatch = function findNodeMatch (node, outerDeco, innerDeco) { | ||
ViewTreeUpdater.prototype.findNodeMatch = function (node, outerDeco, innerDeco) { | ||
var this$1 = this; | ||
// FIXME think about failure cases where updates will redraw too much | ||
for (var i = this.index, children = this.top.children, e = Math.min(children.length, i + 5); i < e; i++) { | ||
@@ -855,10 +944,21 @@ if (children[i].matchesNode(node, outerDeco, innerDeco)) { | ||
// : (Node, [Decoration], DecorationSet, EditorView) → bool | ||
// Try to update the next node, if any, to the given data. | ||
ViewTreeUpdater.prototype.updateNode = function updateNode (node, outerDeco, innerDeco, view) { | ||
// : (Node, [Decoration], DecorationSet, EditorView, Fragment, number) → bool | ||
// Try to update the next node, if any, to the given data. First | ||
// tries scanning ahead in the siblings fragment to see if the next | ||
// node matches any of those, and if so, doesn't touch it, to avoid | ||
// overwriting nodes that could still be used. | ||
ViewTreeUpdater.prototype.updateNextNode = function (node, outerDeco, innerDeco, view, siblings, index) { | ||
if (this.index == this.top.children.length) { return false } | ||
var next = this.top.children[this.index] | ||
if (!(next instanceof NodeViewDesc && next.update(node, outerDeco, innerDeco, view))) { return false } | ||
this.index++ | ||
return true | ||
if (next instanceof NodeViewDesc) { | ||
for (var i = index + 1, e = Math.min(siblings.childCount, i + 5); i < e; i++) | ||
{ if (next.node == siblings.child(i)) { return false } } | ||
var nextDOM = next.dom | ||
if (next.update(node, outerDeco, innerDeco, view)) { | ||
if (next.dom != nextDOM) { this.changed = true } | ||
this.index++ | ||
return true | ||
} | ||
} | ||
return false | ||
}; | ||
@@ -868,3 +968,3 @@ | ||
// Insert the node as a newly created node desc. | ||
ViewTreeUpdater.prototype.addNode = function addNode (node, outerDeco, innerDeco, view) { | ||
ViewTreeUpdater.prototype.addNode = function (node, outerDeco, innerDeco, view) { | ||
this.top.children.splice(this.index++, 0, NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view)) | ||
@@ -874,3 +974,3 @@ this.changed = true | ||
ViewTreeUpdater.prototype.placeWidget = function placeWidget (widget) { | ||
ViewTreeUpdater.prototype.placeWidget = function (widget) { | ||
if (this.index < this.top.children.length && this.top.children[this.index].matchesWidget(widget)) { | ||
@@ -886,19 +986,14 @@ this.index++ | ||
// contentEditable. | ||
ViewTreeUpdater.prototype.addTextblockHacks = function addTextblockHacks () { | ||
var lastChild = this.top.children[this.index - 1], hack | ||
ViewTreeUpdater.prototype.addTextblockHacks = function () { | ||
var lastChild = this.top.children[this.index - 1] | ||
while (lastChild instanceof MarkViewDesc) { lastChild = lastChild.children[lastChild.children.length - 1] } | ||
if (!lastChild || lastChild.dom.nodeName == "BR") | ||
{ hack = "br" } | ||
else if (this.wrapsInPRE()) | ||
{ hack = "newline" } | ||
else if (!(lastChild instanceof TextViewDesc)) | ||
{ hack = "text" } | ||
if (hack) { | ||
if (this.index < this.top.children.length && this.top.children[this.index].matchesHack(hack)) { | ||
if (!lastChild || // Empty textblock | ||
!(lastChild instanceof TextViewDesc) || | ||
/\n$/.test(lastChild.node.text)) { | ||
if (this.index < this.top.children.length && this.top.children[this.index].matchesHack()) { | ||
this.index++ | ||
} else { | ||
var dom = document.createElement(hack == "br" ? "br" : "span") | ||
if (hack == "newline") { dom.textContent = "\n" } | ||
this.top.children.splice(this.index++, 0, new HackViewDesc(this.top, hack, dom)) | ||
var dom = document.createElement("br") | ||
this.top.children.splice(this.index++, 0, new BRHackViewDesc(this.top, nothing, dom, null)) | ||
this.changed = true | ||
@@ -909,10 +1004,2 @@ } | ||
ViewTreeUpdater.prototype.wrapsInPRE = function wrapsInPRE () { | ||
var top = this.top | ||
for (var dom = top.contentDOM;; dom = dom.parentNode) { | ||
if (dom.nodeName == "PRE") { return true } | ||
if (dom == top.dom) { return false } | ||
} | ||
}; | ||
// : (ViewDesc, DecorationSet, (Decoration), (Node, [Decoration], DecorationSet)) | ||
@@ -929,3 +1016,3 @@ // This function abstracts iterating over the nodes and decorations in | ||
var child = parent.child(i) | ||
onNode(child, locals, deco.forChild(offset, child)) | ||
onNode(child, locals, deco.forChild(offset, child), i) | ||
offset += child.nodeSize | ||
@@ -966,3 +1053,3 @@ } | ||
onNode(child$1, active.length ? active.slice() : nothing, deco.forChild(offset, child$1)) | ||
onNode(child$1, active.length ? active.slice() : nothing, deco.forChild(offset, child$1), parentIndex - 1) | ||
offset = end | ||
@@ -969,0 +1056,0 @@ } |
{ | ||
"name": "prosemirror-view", | ||
"version": "0.15.2", | ||
"version": "0.16.0", | ||
"description": "ProseMirror's view component", | ||
@@ -20,8 +20,8 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"prosemirror-model": "^0.15.0", | ||
"prosemirror-state": "^0.15.0", | ||
"prosemirror-transform": "^0.15.0" | ||
"prosemirror-model": "^0.16.0", | ||
"prosemirror-state": "^0.16.0", | ||
"prosemirror-transform": "^0.16.0" | ||
}, | ||
"devDependencies": { | ||
"buble": "~0.14.0", | ||
"buble": "^0.15.1", | ||
"ist": "^1.0.0", | ||
@@ -35,5 +35,5 @@ "mocha": "^3.0.2", | ||
"test-server": "moduleserve test --port 8090", | ||
"build": "rimraf dist && buble -i src -o dist", | ||
"build": "rimraf dist && buble -i src -o dist --no-named-function-expr", | ||
"prepublish": "npm run build" | ||
} | ||
} |
@@ -63,2 +63,4 @@ const {Selection, NodeSelection, TextSelection} = require("prosemirror-state") | ||
else break | ||
} else if (isBlockNode(node)) { | ||
break | ||
} else { | ||
@@ -99,2 +101,4 @@ let prev = node.previousSibling | ||
else break | ||
} else if (isBlockNode(node)) { | ||
break | ||
} else { | ||
@@ -105,3 +109,3 @@ let next = node.nextSibling | ||
moveOffset = Array.prototype.indexOf.call(moveNode.childNodes, next) + 1 | ||
next = next.previousSibling | ||
next = next.nextSibling | ||
} | ||
@@ -122,2 +126,7 @@ if (!next) { | ||
function isBlockNode(dom) { | ||
let desc = dom.pmViewDesc | ||
return desc && desc.node && desc.node.isBlock | ||
} | ||
function setSel(sel, node, offset) { | ||
@@ -124,0 +133,0 @@ let range = document.createRange() |
@@ -124,4 +124,4 @@ function compareObjs(a, b) { | ||
// and reuse DOM nodes. | ||
static widget(pos, widget, options) { | ||
return new Decoration(pos, pos, new WidgetType(widget, options)) | ||
static widget(pos, dom, options) { | ||
return new Decoration(pos, pos, new WidgetType(dom, options)) | ||
} | ||
@@ -128,0 +128,0 @@ |
@@ -34,6 +34,2 @@ const {Fragment, DOMParser} = require("prosemirror-model") | ||
read(range) { | ||
readDOMChange(this, this.state, range) | ||
} | ||
finish(force) { | ||
@@ -46,3 +42,6 @@ clearTimeout(this.timeout) | ||
this.view.inDOMChange = null | ||
this.read(range) | ||
readDOMChange(this, this.state, range) | ||
// If the reading didn't result in a view update, force one by | ||
// resetting the view to its current state. | ||
if (this.view.docView.dirty) this.view.updateState(this.view.state) | ||
} | ||
@@ -177,2 +176,8 @@ | ||
let parseResult, doc = oldState.doc, view = domChange.view | ||
// If there have been changes since this DOM update started, we must | ||
// map our start and end positions, as well as the new selection | ||
// positions, through them. | ||
let mapping = domChange.mappings.getMapping(view.state) | ||
if (!mapping) return | ||
for (;;) { | ||
@@ -189,4 +194,11 @@ parseResult = parseBetween(view, oldState, range.from, range.to) | ||
let change = findDiff(compare.content, parsed.content, range.from, oldState.selection.from) | ||
if (!change) return false | ||
if (!change) { | ||
if (parsedSel) { | ||
let sel = resolveSelection(view.state.doc, mapping, parsedSel) | ||
if (!sel.eq(view.state.selection)) view.props.onAction(sel.action()) | ||
} | ||
return | ||
} | ||
let $from = parsed.resolveNoCache(change.start - range.from) | ||
@@ -207,15 +219,5 @@ let $to = parsed.resolveNoCache(change.endB - range.from) | ||
let from = change.start, to = change.endA | ||
// If there have been changes since this DOM update started, we must | ||
// map our start and end positions, as well as the new selection | ||
// positions, through them. | ||
let mapping = domChange.mappings.getMapping(view.state), $from1 | ||
if (!mapping) return | ||
let from = mapping.map(change.start), to = mapping.map(change.endA, -1) | ||
from = mapping.map(from) | ||
to = mapping.map(to) | ||
if (parsedSel) parsedSel = {anchor: mapping.map(parsedSel.anchor), | ||
head: mapping.map(parsedSel.head)} | ||
let tr = view.state.tr, handled = false, markChange | ||
let tr = view.state.tr, handled = false, markChange, $from1 | ||
if ($from.sameParent($to) && $from.parent.isTextblock && $from.pos != $to.pos) { | ||
@@ -241,8 +243,11 @@ if (change.endA == change.endB && | ||
tr.replace(from, to, parsed.slice(change.start - range.from, change.endB - range.from)) | ||
if (parsedSel) | ||
tr.setSelection(Selection.between(tr.doc.resolve(parsedSel.anchor), | ||
tr.doc.resolve(parsedSel.head))) | ||
if (parsedSel) tr.setSelection(resolveSelection(tr.doc, mapping, parsedSel)) | ||
view.props.onAction(tr.scrollAction()) | ||
} | ||
function resolveSelection(doc, mapping, parsedSel) { | ||
return Selection.between(doc.resolve(mapping.map(parsedSel.anchor)), | ||
doc.resolve(mapping.map(parsedSel.head))) | ||
} | ||
// : (Fragment, Fragment) → ?{mark: Mark, type: string} | ||
@@ -249,0 +254,0 @@ // Given two same-length, non-empty fragments of inline content, |
126
src/index.js
@@ -7,3 +7,3 @@ const {EditorState} = require("prosemirror-state") | ||
const {SelectionReader, selectionToDOM} = require("./selection") | ||
const {viewDecorations} = require("./decoration") | ||
const {viewDecorations, Decoration} = require("./decoration") | ||
@@ -40,3 +40,3 @@ ;({Decoration: exports.Decoration, DecorationSet: exports.DecorationSet} = require("./decoration")) | ||
// :: dom.Node | ||
// :: dom.Element | ||
// The editable DOM node containing the document. (You probably | ||
@@ -46,9 +46,7 @@ // should not be directly interfering with its child nodes.) | ||
this.updateDOMForProps() | ||
if (place && place.appendChild) place.appendChild(this.content) | ||
else if (place) place(this.content) | ||
this.docView = docViewDesc(this.state.doc, viewDecorations(this), this.content, this) | ||
this.content.contentEditable = true | ||
this.editable = getEditable(this) | ||
this.docView = docViewDesc(this.state.doc, computeDocDeco(this), viewDecorations(this), this.content, this) | ||
@@ -58,2 +56,5 @@ this.lastSelectedViewDesc = null | ||
initInput(this) | ||
this.pluginViews = [] | ||
this.updatePluginViews() | ||
} | ||
@@ -78,19 +79,18 @@ | ||
let redrawn = false | ||
let decorations = viewDecorations(this) | ||
let prevEditable = this.editable | ||
this.editable = getEditable(this) | ||
let innerDeco = viewDecorations(this), outerDeco = computeDocDeco(this) | ||
if (!this.docView.matchesNode(state.doc, [], decorations)) { | ||
if (!this.docView.matchesNode(state.doc, outerDeco, innerDeco)) { | ||
stopObserving(this) | ||
this.docView.update(state.doc, [], decorations, this) | ||
this.docView.update(state.doc, outerDeco, innerDeco, this) | ||
startObserving(this) | ||
redrawn = true | ||
} | ||
if (redrawn || !state.selection.eq(prev.selection)) | ||
if (!state.selection.eq(prev.selection) || this.selectionReader.domChanged()) | ||
selectionToDOM(this, state.selection) | ||
this.updateDOMForProps() | ||
if (prevEditable != this.editable) this.selectionReader.editableChanged() | ||
this.updatePluginViews(prev) | ||
// FIXME somehow schedule this relative to ui/update so that it | ||
// doesn't cause extra layout | ||
if (state.scrollToSelection > prev.scrollToSelection || prev.config != state.config) | ||
@@ -100,19 +100,21 @@ scrollPosIntoView(this, state.selection.head == null ? state.selection.from : state.selection.from) | ||
updateDOMForProps() { | ||
let spellcheck = !!this.someProp("spellcheck") | ||
if (spellcheck != this.content.spellcheck) this.content.spellcheck = spellcheck | ||
let label = this.someProp("label", f => f(this.state)) || "" | ||
if (this.content.getAttribute("aria-label") != label) this.content.setAttribute("aria-label", label) | ||
destroyPluginViews() { | ||
let view | ||
while (view = this.pluginViews.pop()) if (view.destroy) view.destroy() | ||
} | ||
let classes = ["ProseMirror", "ProseMirror-content"] // FIXME remove backwards-compat class | ||
if (this.focused) classes.push("ProseMirror-focused") | ||
if (this.state.selection.node) classes.push("ProseMirror-nodeselection") | ||
this.someProp("class", f => { | ||
let cls = f(this.state) | ||
if (!cls) return | ||
let array = cls.split(" ") | ||
for (let i = 0; i < array.length; i++) classes.push(array[i]) | ||
}) | ||
let className = classes.sort().join(" ") | ||
if (this.content.className != className) this.content.className = className | ||
updatePluginViews(prevState) { | ||
let plugins = this.state.plugins | ||
if (!prevState || prevState.plugins != plugins) { | ||
this.destroyPluginViews() | ||
for (let i = 0; i < plugins.length; i++) { | ||
let plugin = plugins[i] | ||
if (plugin.options.view) this.pluginViews.push(plugin.options.view(this)) | ||
} | ||
} else { | ||
for (let i = 0; i < this.pluginViews.length; i++) { | ||
let pluginView = this.pluginViews[i] | ||
if (pluginView.update) pluginView.update(this) | ||
} | ||
} | ||
} | ||
@@ -123,3 +125,3 @@ | ||
hasFocus() { | ||
if (this.content.ownerDocument.activeElement != this.content) return false | ||
if (this.editable && this.content.ownerDocument.activeElement != this.content) return false | ||
let sel = this.root.getSelection() | ||
@@ -138,3 +140,3 @@ return sel.rangeCount && this.content.contains(sel.anchorNode.nodeType == 3 ? sel.anchorNode.parentNode : sel.anchorNode) | ||
let prop = this.props && this.props[propName], value | ||
if (prop && (value = f ? f(prop) : prop)) return value | ||
if (prop != null && (value = f ? f(prop) : prop)) return value | ||
let plugins = this.state.plugins | ||
@@ -151,3 +153,3 @@ if (plugins) for (let i = 0; i < plugins.length; i++) { | ||
selectionToDOM(this, this.state.selection, true) | ||
this.content.focus() | ||
if (this.editable) this.content.focus() | ||
} | ||
@@ -199,3 +201,5 @@ | ||
destroy() { | ||
this.destroyPluginViews() | ||
this.docView.destroy() | ||
this.selectionReader.destroy() | ||
EditorState.removeApplyListener(this.trackState) | ||
@@ -212,2 +216,25 @@ if (this.content.parentNode) this.content.parentNode.removeChild(this.content) | ||
function computeDocDeco(view) { | ||
let attrs = Object.create(null) | ||
attrs.class = "ProseMirror" + (view.focused ? " ProseMirror-focused" : "") + | ||
(view.state.selection.node ? " ProseMirror-nodeselection" : "") | ||
attrs.contenteditable = String(view.editable) | ||
view.someProp("attributes", value => { | ||
if (typeof value == "function") value = value(view.state) | ||
if (value) for (let attr in value) { | ||
if (attr == "class") | ||
attrs.class += " " + value[attr] | ||
else if (!attrs[attr] && attr != "contenteditable" && attr != "nodeName") | ||
attrs[attr] = String(value[attr]) | ||
} | ||
}) | ||
return [Decoration.node(0, view.state.doc.content.size, attrs)] | ||
} | ||
function getEditable(view) { | ||
return !view.someProp("editable", value => value(view.state) === false) | ||
} | ||
// EditorProps:: interface | ||
@@ -311,3 +338,3 @@ // | ||
// | ||
// nodeViews:: ?Object<(node: Node, view: EditorView, getPos: () → number) → NodeView> | ||
// nodeViews:: ?Object<(node: Node, view: EditorView, getPos: () → number, decorations: [Decoration]) → NodeView> | ||
// Allows you to pass custom rendering and behavior logic for nodes | ||
@@ -320,2 +347,8 @@ // and marks. Should map node and mark names to constructor function | ||
// | ||
// `decorations` is an array of node or inline decorations that are | ||
// active around the node. They are automatically drawn in the | ||
// normal way, and you will usually just want to ignore this, but | ||
// they can also be used as a way to provide context information to | ||
// the node view without adding it to the document itself. | ||
// | ||
// clipboardSerializer:: ?DOMSerializer | ||
@@ -331,15 +364,16 @@ // The DOM serializer to use when putting content onto the | ||
// | ||
// spellcheck:: ?bool | ||
// Controls whether the DOM spellcheck attribute is enabled on the | ||
// editable content. Defaults to false. | ||
// editable:: ?(EditorState) → bool | ||
// When this returns false, the content of the view is not directly | ||
// editable. | ||
// | ||
// class:: ?(state: EditorState) → ?string | ||
// Controls the CSS class name of the editor DOM node. Any classes | ||
// returned from this will be added to the default `ProseMirror` | ||
// class. | ||
// attributes:: ?union<Object<string>, (EditorState) → ?Object<string>> | ||
// Control the DOM attributes of the editable element. May be either | ||
// an object or a function going from an editor state to an object. | ||
// By default, the element will get a class `"ProseMirror"`, and | ||
// will have its `contentEditable` attribute determined by the | ||
// [`editable` prop](#view.EditorProps.editable). Additional classes | ||
// provided here will be added to the class. For other attributes, | ||
// the value provided first (as in | ||
// [`someProp`](#view.EditorView.someProp)) will be used. | ||
// | ||
// label:: ?(state: EditorState) → ?string | ||
// Can be used to set an `aria-label` attribute on the editable | ||
// content node. | ||
// | ||
// scrollThreshold:: ?number | ||
@@ -346,0 +380,0 @@ // Determines the distance (in pixels) between the cursor and the |
@@ -11,3 +11,3 @@ const {Selection, NodeSelection, TextSelection} = require("prosemirror-state") | ||
// to invoke when the event fires. | ||
const handlers = {} | ||
const handlers = {}, editHandlers = {} | ||
@@ -26,3 +26,4 @@ function initInput(view) { | ||
view.content.addEventListener(event, event => { | ||
if (eventBelongsToView(view, event) && !view.someProp("handleDOMEvent", f => f(view, event))) | ||
if ((view.editable || !(event.type in editHandlers)) && | ||
eventBelongsToView(view, event) && !view.someProp("handleDOMEvent", f => f(view, event))) | ||
handler(view, event) | ||
@@ -50,3 +51,3 @@ }) | ||
handlers.keydown = (view, event) => { | ||
editHandlers.keydown = (view, event) => { | ||
if (event.keyCode == 16) view.shiftKey = true | ||
@@ -60,7 +61,7 @@ if (view.inDOMChange) return | ||
handlers.keyup = (view, e) => { | ||
editHandlers.keyup = (view, e) => { | ||
if (e.keyCode == 16) view.shiftKey = false | ||
} | ||
handlers.keypress = (view, event) => { | ||
editHandlers.keypress = (view, event) => { | ||
if (view.inDOMChange || !event.charCode || | ||
@@ -268,4 +269,3 @@ event.ctrlKey && !event.altKey || browser.mac && event.metaKey) return | ||
} else if (this.flushed) { | ||
this.view.focus() | ||
this.view.props.onAction(Selection.near(this.view.state.doc.resolve(this.pos.pos)).action({origin: "mouse"})) | ||
updateSelection(this.view, Selection.near(this.view.state.doc.resolve(this.pos.pos)), "mouse") | ||
event.preventDefault() | ||
@@ -311,3 +311,3 @@ } else { | ||
handlers.compositionstart = handlers.compositionupdate = view => { | ||
editHandlers.compositionstart = editHandlers.compositionupdate = view => { | ||
DOMChange.start(view, true) | ||
@@ -317,3 +317,3 @@ if (view.state.storedMarks) view.inDOMChange.finish(true) | ||
handlers.compositionend = (view, e) => { | ||
editHandlers.compositionend = (view, e) => { | ||
if (!view.inDOMChange) { | ||
@@ -343,6 +343,5 @@ // We received a compositionend without having seen any previous | ||
function registerMutations(view, mutations) { | ||
for (let i = 0; i < mutations.length; i++) { | ||
let mut = mutations[i] | ||
if (mut.target == view.content && mut.type == "attributes") continue | ||
let desc = view.docView.nearestDesc(mut.target) | ||
if (view.editable) for (let i = 0; i < mutations.length; i++) { | ||
let mut = mutations[i], desc = view.docView.nearestDesc(mut.target) | ||
if (desc == view.docView && mut.type == "attributes") continue | ||
if (!desc || desc.ignoreMutation(mut)) continue | ||
@@ -371,5 +370,5 @@ | ||
handlers.input = view => DOMChange.start(view) | ||
editHandlers.input = view => DOMChange.start(view) | ||
handlers.copy = handlers.cut = (view, e) => { | ||
handlers.copy = editHandlers.cut = (view, e) => { | ||
let sel = view.state.selection, cut = e.type == "cut" | ||
@@ -390,3 +389,3 @@ if (sel.empty) return | ||
handlers.paste = (view, e) => { | ||
editHandlers.paste = (view, e) => { | ||
if (!e.clipboardData) { | ||
@@ -449,5 +448,7 @@ if (browser.ie && browser.ie_version <= 11) DOMChange.start(view) | ||
handlers.dragover = handlers.dragenter = (_, e) => e.preventDefault() | ||
editHandlers.dragover = editHandlers.dragenter = (_, e) => e.preventDefault() | ||
handlers.drop = (view, e) => { | ||
editHandlers.dragleave = () => null | ||
editHandlers.drop = (view, e) => { | ||
let dragging = view.dragging | ||
@@ -496,1 +497,4 @@ view.dragging = null | ||
} | ||
// Make sure all handlers get registered | ||
for (let prop in editHandlers) handlers[prop] = editHandlers[prop] |
@@ -17,8 +17,17 @@ const {Selection, NodeSelection} = require("prosemirror-state") | ||
view.content.addEventListener("focus", () => this.poller.receivedFocus()) | ||
view.content.addEventListener("blur", () => this.poller.lostFocus()) | ||
view.content.addEventListener("focus", () => this.poller.start()) | ||
view.content.addEventListener("blur", () => this.poller.stop()) | ||
if (!view.editable) this.poller.start() | ||
} | ||
destroy() { this.poller.stop() } | ||
poll(origin) { this.poller.poll(origin) } | ||
editableChanged() { | ||
if (!this.view.editable) this.poller.start() | ||
else if (!this.view.hasFocus()) this.poller.stop() | ||
} | ||
// : () → bool | ||
@@ -70,68 +79,72 @@ // Whether the DOM selection has changed from the last known state. | ||
function poller(reader) { | ||
// There's two polling models. On browsers that support the | ||
// selectionchange event (everything except Firefox, basically), we | ||
// register a listener for that whenever the editor is focused. | ||
if ("onselectionchange" in document) return new class { | ||
constructor() { | ||
this.listening = false | ||
this.curOrigin = null | ||
this.originTime = 0 | ||
// There's two polling models. On browsers that support the | ||
// selectionchange event (everything except Firefox, basically), we | ||
// register a listener for that whenever the editor is focused. | ||
class SelectionChangePoller { | ||
constructor(reader) { | ||
this.listening = false | ||
this.curOrigin = null | ||
this.originTime = 0 | ||
this.readFunc = () => reader.readFromDOM(this.originTime > Date.now() - 50 ? this.curOrigin : null) | ||
} | ||
this.readFunc = () => reader.readFromDOM(this.originTime > Date.now() - 50 ? this.curOrigin : null) | ||
} | ||
poll(origin) { | ||
this.curOrigin = origin | ||
this.originTime = Date.now() | ||
} | ||
poll(origin) { | ||
this.curOrigin = origin | ||
this.originTime = Date.now() | ||
} | ||
receivedFocus() { | ||
if (!this.listening) { | ||
document.addEventListener("selectionchange", this.readFunc) | ||
this.listening = true | ||
} | ||
start() { | ||
if (!this.listening) { | ||
document.addEventListener("selectionchange", this.readFunc) | ||
this.listening = true | ||
} | ||
} | ||
lostFocus() { | ||
if (this.listening) { | ||
document.removeEventListener("selectionchange", this.readFunc) | ||
this.listening = false | ||
} | ||
stop() { | ||
if (this.listening) { | ||
document.removeEventListener("selectionchange", this.readFunc) | ||
this.listening = false | ||
} | ||
} | ||
// On Firefox, we use timeout-based polling. | ||
return new class { | ||
constructor() { | ||
// The timeout ID for the poller when active. | ||
this.polling = null | ||
this.reader = reader | ||
this.pollFunc = this.doPoll.bind(this, null) | ||
} | ||
} | ||
doPoll(origin) { | ||
if (this.reader.view.hasFocus()) { | ||
this.reader.readFromDOM(origin) | ||
this.polling = setTimeout(this.pollFunc, 100) | ||
} else { | ||
this.polling = null | ||
} | ||
} | ||
// On Firefox, we use timeout-based polling. | ||
class TimeoutPoller { | ||
constructor(reader) { | ||
// The timeout ID for the poller when active. | ||
this.polling = null | ||
this.reader = reader | ||
this.pollFunc = this.doPoll.bind(this, null) | ||
} | ||
poll(origin) { | ||
clearTimeout(this.polling) | ||
this.polling = setTimeout(origin ? this.doPoll.bind(this, origin) : this.pollFunc, 0) | ||
doPoll(origin) { | ||
let view = this.reader.view | ||
if (view.focused || !view.editable) { | ||
this.reader.readFromDOM(origin) | ||
this.polling = setTimeout(this.pollFunc, 100) | ||
} else { | ||
this.polling = null | ||
} | ||
} | ||
receivedFocus() { | ||
if (this.polling == null) this.poll() | ||
} | ||
poll(origin) { | ||
clearTimeout(this.polling) | ||
this.polling = setTimeout(origin ? this.doPoll.bind(this, origin) : this.pollFunc, 0) | ||
} | ||
lostFocus() { | ||
clearTimeout(this.polling) | ||
this.polling = null | ||
} | ||
start() { | ||
if (this.polling == null) this.poll() | ||
} | ||
stop() { | ||
clearTimeout(this.polling) | ||
this.polling = null | ||
} | ||
} | ||
function poller(reader) { | ||
return new ("onselectionchange" in document ? SelectionChangePoller : TimeoutPoller)(reader) | ||
} | ||
function selectionToDOM(view, sel, takeFocus) { | ||
@@ -143,3 +156,3 @@ syncNodeSelection(view, sel) | ||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=921444 | ||
else if (browser.gecko) view.content.focus() | ||
else if (browser.gecko && view.editable) view.content.focus() | ||
} | ||
@@ -146,0 +159,0 @@ |
@@ -26,10 +26,11 @@ const {DOMSerializer} = require("prosemirror-model") | ||
// | ||
// update:: ?(node: Node, deco: DecorationSet) → bool | ||
// update:: ?(node: Node, decorations: [Decoration]) → bool | ||
// When given, this will be called when the view is updating itself. | ||
// It will be given a node (possibly of a different type), and a | ||
// decoration set (which it may ignore, if it chooses not to support | ||
// decorations), and should return true if it was able to update to | ||
// that node, and false otherwise. If the node view has a | ||
// `contentDOM` property (or no `dom` property), updating its child | ||
// nodes will be handled by ProseMirror. | ||
// It will be given a node (possibly of a different type), and an | ||
// array of active decorations (which are automatically drawn, and | ||
// the node view may ignore if it isn't interested in them), and | ||
// should return true if it was able to update to that node, and | ||
// false otherwise. If the node view has a `contentDOM` property (or | ||
// no `dom` property), updating its child nodes will be handled by | ||
// ProseMirror. | ||
// | ||
@@ -197,5 +198,8 @@ // selectNode:: ?() | ||
nearestDesc(dom) { | ||
for (; dom; dom = dom.parentNode) { | ||
let desc = this.getDesc(dom) | ||
if (desc) return desc | ||
for (let first = true, cur = dom; cur; cur = cur.parentNode) { | ||
let desc = this.getDesc(cur) | ||
if (desc) { | ||
if (first && desc.nodeDOM && !desc.nodeDOM.contains(dom)) first = false | ||
else return desc | ||
} | ||
} | ||
@@ -252,3 +256,5 @@ } | ||
for (let j = i - 1; j >= 0; j--) { | ||
let found = Array.prototype.indexOf.call(content.childNodes, this.children[j].dom) | ||
let child = this.children[j] | ||
if (!child.size) continue | ||
let found = Array.prototype.indexOf.call(content.childNodes, child.dom) | ||
if (found > -1) return found + 1 | ||
@@ -259,3 +265,5 @@ } | ||
for (let j = i; j < this.children.length; j++) { | ||
let found = Array.prototype.indexOf.call(content.childNodes, this.children[j].dom) | ||
let child = this.children[j] | ||
if (!child.size) continue | ||
let found = Array.prototype.indexOf.call(content.childNodes, child.dom) | ||
if (found > -1) return found | ||
@@ -321,3 +329,3 @@ } | ||
let child = this.children[i], end = offset + child.size | ||
if (from < end && to > offset) { | ||
if (offset == end ? from <= end && to >= offset : from < end && to > offset) { | ||
let startInside = offset + child.border, endInside = end - child.border | ||
@@ -390,4 +398,5 @@ if (from >= startInside && to <= endInside) { | ||
// : (?ViewDesc, Node, [Decoration], DecorationSet, dom.Node, ?dom.Node, EditorView) | ||
constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, view) { | ||
constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view) { | ||
super(parent, node.isLeaf ? nothing : [], dom, contentDOM) | ||
this.nodeDOM = nodeDOM | ||
this.node = node | ||
@@ -413,18 +422,18 @@ this.outerDeco = outerDeco | ||
// own position) | ||
if (!descObj) return parent.posAtStart + parent.size | ||
if (descObj.parent) return descObj.parent.posBeforeChild(descObj) | ||
}) | ||
if (descObj && descObj.parent) return descObj.parent.posBeforeChild(descObj) | ||
}, outerDeco) | ||
let dom = spec && spec.dom, contentDOM = spec && spec.contentDOM | ||
if (!dom) ({dom, contentDOM} = DOMSerializer.renderSpec(document, node.type.spec.toDOM(node))) | ||
let startDOM = dom | ||
for (let i = 0; i < outerDeco.length; i++) | ||
dom = applyOuterDeco(dom, outerDeco[i].type.attrs, node) | ||
if (!contentDOM && !node.isText) dom.contentEditable = false | ||
let nodeDOM = dom | ||
dom = applyOuterDeco(dom, outerDeco, node, !!contentDOM) | ||
if (spec) | ||
return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, spec, view) | ||
return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, spec, view) | ||
else if (node.isText) | ||
return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, startDOM, view) | ||
return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM, view) | ||
else | ||
return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, view) | ||
return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view) | ||
} | ||
@@ -453,3 +462,3 @@ | ||
updater.placeWidget(widget) | ||
}, (child, outerDeco, innerDeco) => { | ||
}, (child, outerDeco, innerDeco, i) => { | ||
// Make sure the wrapping mark descs match the node's marks. | ||
@@ -461,3 +470,3 @@ updater.syncToMarks(child.marks, view) | ||
// Or try updating the next desc to reflect this node. | ||
updater.updateNode(child, outerDeco, innerDeco, view) || | ||
updater.updateNextNode(child, outerDeco, innerDeco, view, this.node.content, i) || | ||
// Or just add it as a new desc. | ||
@@ -485,4 +494,4 @@ updater.addNode(child, outerDeco, innerDeco, view) | ||
if (this.dirty == NODE_DIRTY || | ||
!node.sameMarkup(this.node) || | ||
!sameOuterDeco(outerDeco, this.outerDeco)) return false | ||
!node.sameMarkup(this.node)) return false | ||
this.updateOuterDeco(outerDeco) | ||
this.node = node | ||
@@ -495,5 +504,14 @@ this.innerDeco = innerDeco | ||
updateOuterDeco(outerDeco) { | ||
if (sameOuterDeco(outerDeco, this.outerDeco)) return | ||
let content = !!this.contentDOM, needsWrap = this.nodeDOM.nodeType != 1 | ||
this.dom = patchOuterDeco(this.dom, this.nodeDOM, | ||
computeOuterDeco(this.outerDeco, this.node, content, needsWrap), | ||
computeOuterDeco(outerDeco, this.node, content, needsWrap)) | ||
this.outerDeco = outerDeco | ||
} | ||
// Mark this node as being the selected node. | ||
selectNode() { | ||
this.dom.classList.add("ProseMirror-selectednode") | ||
this.nodeDOM.classList.add("ProseMirror-selectednode") | ||
} | ||
@@ -503,3 +521,3 @@ | ||
deselectNode() { | ||
this.dom.classList.remove("ProseMirror-selectednode") | ||
this.nodeDOM.classList.remove("ProseMirror-selectednode") | ||
} | ||
@@ -510,4 +528,5 @@ } | ||
// and used by the view class. | ||
function docViewDesc(doc, deco, dom, view) { | ||
return new NodeViewDesc(null, doc, nothing, deco, dom, dom, view) | ||
function docViewDesc(doc, outerDeco, innerDeco, dom, view) { | ||
applyOuterDeco(dom, outerDeco, doc, true) | ||
return new NodeViewDesc(null, doc, outerDeco, innerDeco, dom, dom, dom, view) | ||
} | ||
@@ -517,9 +536,8 @@ exports.docViewDesc = docViewDesc | ||
class TextViewDesc extends NodeViewDesc { | ||
constructor(parent, node, outerDeco, innerDeco, dom, textDOM, view) { | ||
super(parent, node, outerDeco, innerDeco, dom, null, view) | ||
this.textDOM = textDOM | ||
constructor(parent, node, outerDeco, innerDeco, dom, nodeDOM, view) { | ||
super(parent, node, outerDeco, innerDeco, dom, null, nodeDOM, view) | ||
} | ||
parseRule() { | ||
return {skip: this.textDOM.parentNode} | ||
return {skip: this.nodeDOM.parentNode} | ||
} | ||
@@ -529,6 +547,6 @@ | ||
if (this.dirty == NODE_DIRTY || (this.dirty != NOT_DIRTY && !this.inParent) || | ||
!node.sameMarkup(this.node) || | ||
!sameOuterDeco(outerDeco, this.outerDeco)) return false | ||
if ((this.dirty != NOT_DIRTY || node.text != this.node.text) && node.text != this.textDOM.nodeValue) | ||
this.textDOM.nodeValue = node.text | ||
!node.sameMarkup(this.node)) return false | ||
this.updateOuterDeco(outerDeco) | ||
if ((this.dirty != NOT_DIRTY || node.text != this.node.text) && node.text != this.nodeDOM.nodeValue) | ||
this.nodeDOM.nodeValue = node.text | ||
this.node = node | ||
@@ -541,3 +559,3 @@ this.dirty = NOT_DIRTY | ||
let parentDOM = this.parent.contentDOM | ||
for (let n = this.textDOM; n; n = n.parentNode) if (n == parentDOM) return true | ||
for (let n = this.nodeDOM; n; n = n.parentNode) if (n == parentDOM) return true | ||
return false | ||
@@ -547,7 +565,7 @@ } | ||
domFromPos(pos, searchDOM) { | ||
return {node: this.textDOM, offset: searchDOM ? Math.max(pos, this.textDOM.nodeValue.length) : pos} | ||
return {node: this.nodeDOM, offset: searchDOM ? Math.max(pos, this.nodeDOM.nodeValue.length) : pos} | ||
} | ||
localPosFromDOM(dom, offset, bias) { | ||
if (dom == this.textDOM) return this.posAtStart + Math.min(offset, this.node.text.length) | ||
if (dom == this.nodeDOM) return this.posAtStart + Math.min(offset, this.node.text.length) | ||
return super.localPosFromDOM(dom, offset, bias) | ||
@@ -563,9 +581,5 @@ } | ||
// around contentEditable terribleness. | ||
class HackViewDesc extends ViewDesc { | ||
constructor(parent, type, dom) { | ||
super(parent, nothing, dom, null) | ||
this.type = type | ||
} | ||
class BRHackViewDesc extends ViewDesc { | ||
parseRule() { return {ignore: true} } | ||
matchesHack(type) { return this.dirty == NOT_DIRTY && this.type == type } | ||
matchesHack() { return this.dirty == NOT_DIRTY } | ||
} | ||
@@ -578,4 +592,4 @@ | ||
// : (?ViewDesc, Node, [Decoration], DecorationSet, dom.Node, ?dom.Node, NodeView, EditorView) | ||
constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, spec, view) { | ||
super(parent, node, outerDeco, innerDeco, dom, contentDOM, view) | ||
constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, spec, view) { | ||
super(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view) | ||
this.spec = spec | ||
@@ -589,3 +603,3 @@ } | ||
if (this.spec.update) { | ||
let result = this.spec.update(node, innerDeco) | ||
let result = this.spec.update(node, outerDeco) | ||
if (result) { | ||
@@ -596,4 +610,6 @@ this.node = node | ||
return result | ||
} else if (!this.contentDOM && !node.isLeaf) { | ||
return false | ||
} else { | ||
return super.update(node, outerDeco, innerDeco, view) | ||
return super.update(node, outerDeco, this.contentDOM ? this.innerDeco : innerDeco, view) | ||
} | ||
@@ -648,20 +664,91 @@ } | ||
// : (dom.Node, Object, Node) → dom.Node | ||
// Apply the extra attributes and potentially add a wrapper for a | ||
// decoration. | ||
function applyOuterDeco(dom, attrs, node) { | ||
if (attrs.nodeName || dom.nodeType != 1) { | ||
let wrap = document.createElement(attrs.nodeName || (node.isInline ? "span" : "div")) | ||
wrap.appendChild(dom) | ||
dom = wrap | ||
class OuterDecoLevel { | ||
constructor(nodeName) { | ||
if (nodeName) this.nodeName = nodeName | ||
} | ||
for (let name in attrs) { | ||
let val = attrs[name] | ||
if (name == "class") dom.classList.add(...val.split(" ")) | ||
else if (name == "style") dom.style.cssText += ";" + val | ||
else if (name != "nodeName") dom.setAttribute(name, val) | ||
} | ||
OuterDecoLevel.prototype = Object.create(null) | ||
const noDeco = [new OuterDecoLevel] | ||
function computeOuterDeco(outerDeco, node, hasContent, needsWrap) { | ||
if (hasContent && outerDeco.length == 0) return noDeco | ||
let top = needsWrap ? noDeco[0] : new OuterDecoLevel, result = [top] | ||
if (outerDeco.length) { | ||
for (let i = 0; i < outerDeco.length; i++) { | ||
let attrs = outerDeco[i].type.attrs, cur = top | ||
if (!attrs) continue | ||
if (attrs.nodeName) | ||
result.push(cur = new OuterDecoLevel(attrs.nodeName)) | ||
for (let name in attrs) { | ||
let val = attrs[name] | ||
if (val == null) continue | ||
if (needsWrap && result.length == 1) | ||
result.push(cur = top = new OuterDecoLevel(node.isInline ? "span" : "div")) | ||
if (name == "class") cur.class = (cur.class ? cur.class + " " : "") + val | ||
else if (name == "style") cur.style = (cur.style ? cur.style + ";" : "") + val | ||
else if (name != "nodeName") cur[name] = val | ||
} | ||
} | ||
} | ||
return dom | ||
if (!hasContent && node.isBlock && result.length == 1 && !result[0].nodeName) | ||
result.push(new OuterDecoLevel("div")) | ||
return result | ||
} | ||
function patchOuterDeco(outerDOM, nodeDOM, prevComputed, curComputed) { | ||
// Shortcut for trivial case | ||
if (prevComputed == noDeco && curComputed == noDeco) return nodeDOM | ||
let curDOM = nodeDOM | ||
for (let i = 0; i < curComputed.length; i++) { | ||
let deco = curComputed[i], prev = prevComputed[i] | ||
if (i) { | ||
let parent | ||
if (prev && prev.nodeName == deco.nodeName && curDOM != outerDOM && | ||
(parent = nodeDOM.parentNode) && parent.tagName.toLowerCase() == deco.nodeName) { | ||
curDOM = parent | ||
} else { | ||
parent = document.createElement(deco.nodeName) | ||
parent.appendChild(curDOM) | ||
curDOM = parent | ||
} | ||
} | ||
patchAttributes(curDOM, prev || noDeco[0], deco) | ||
} | ||
return curDOM | ||
} | ||
function patchAttributes(dom, prev, cur) { | ||
for (let name in prev) | ||
if (name != "class" && name != "style" && name != "nodeName" && !(name in cur)) | ||
dom.removeAttribute(name) | ||
for (let name in cur) | ||
if (name != "class" && name != "style" && name != "nodeName" && cur[name] != prev[name]) | ||
dom.setAttribute(name, cur[name]) | ||
if (prev.class != cur.class) { | ||
let prevList = prev.class ? prev.class.split(" ") : nothing | ||
let curList = cur.class ? cur.class.split(" ") : nothing | ||
for (let i = 0; i < prevList.length; i++) if (curList.indexOf(prevList[i]) == -1) | ||
dom.classList.remove(prevList[i]) | ||
for (let i = 0; i < curList.length; i++) if (prevList.indexOf(curList[i]) == -1) | ||
dom.classList.add(curList[i]) | ||
} | ||
if (prev.style != cur.style) { | ||
let text = dom.style.cssText, found | ||
if (prev.style && (found = text.index(prev.style)) > -1) | ||
text = text.slice(0, found) + text.slice(found + prev.style.length) | ||
dom.style.cssText = text + (cur.style || "") | ||
} | ||
} | ||
function applyOuterDeco(dom, deco, node, hasContent) { | ||
return patchOuterDeco(dom, dom, noDeco, computeOuterDeco(deco, node, hasContent, dom.nodeType != 1)) | ||
} | ||
// : ([Decoration], [Decoration]) → bool | ||
@@ -748,3 +835,2 @@ function sameOuterDeco(a, b) { | ||
findNodeMatch(node, outerDeco, innerDeco) { | ||
// FIXME think about failure cases where updates will redraw too much | ||
for (let i = this.index, children = this.top.children, e = Math.min(children.length, i + 5); i < e; i++) { | ||
@@ -760,10 +846,21 @@ if (children[i].matchesNode(node, outerDeco, innerDeco)) { | ||
// : (Node, [Decoration], DecorationSet, EditorView) → bool | ||
// Try to update the next node, if any, to the given data. | ||
updateNode(node, outerDeco, innerDeco, view) { | ||
// : (Node, [Decoration], DecorationSet, EditorView, Fragment, number) → bool | ||
// Try to update the next node, if any, to the given data. First | ||
// tries scanning ahead in the siblings fragment to see if the next | ||
// node matches any of those, and if so, doesn't touch it, to avoid | ||
// overwriting nodes that could still be used. | ||
updateNextNode(node, outerDeco, innerDeco, view, siblings, index) { | ||
if (this.index == this.top.children.length) return false | ||
let next = this.top.children[this.index] | ||
if (!(next instanceof NodeViewDesc && next.update(node, outerDeco, innerDeco, view))) return false | ||
this.index++ | ||
return true | ||
if (next instanceof NodeViewDesc) { | ||
for (let i = index + 1, e = Math.min(siblings.childCount, i + 5); i < e; i++) | ||
if (next.node == siblings.child(i)) return false | ||
let nextDOM = next.dom | ||
if (next.update(node, outerDeco, innerDeco, view)) { | ||
if (next.dom != nextDOM) this.changed = true | ||
this.index++ | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
@@ -790,18 +887,13 @@ | ||
addTextblockHacks() { | ||
let lastChild = this.top.children[this.index - 1], hack | ||
let lastChild = this.top.children[this.index - 1] | ||
while (lastChild instanceof MarkViewDesc) lastChild = lastChild.children[lastChild.children.length - 1] | ||
if (!lastChild || lastChild.dom.nodeName == "BR") | ||
hack = "br" | ||
else if (this.wrapsInPRE()) | ||
hack = "newline" | ||
else if (!(lastChild instanceof TextViewDesc)) | ||
hack = "text" | ||
if (hack) { | ||
if (this.index < this.top.children.length && this.top.children[this.index].matchesHack(hack)) { | ||
if (!lastChild || // Empty textblock | ||
!(lastChild instanceof TextViewDesc) || | ||
/\n$/.test(lastChild.node.text)) { | ||
if (this.index < this.top.children.length && this.top.children[this.index].matchesHack()) { | ||
this.index++ | ||
} else { | ||
let dom = document.createElement(hack == "br" ? "br" : "span") | ||
if (hack == "newline") dom.textContent = "\n" | ||
this.top.children.splice(this.index++, 0, new HackViewDesc(this.top, hack, dom)) | ||
let dom = document.createElement("br") | ||
this.top.children.splice(this.index++, 0, new BRHackViewDesc(this.top, nothing, dom, null)) | ||
this.changed = true | ||
@@ -811,10 +903,2 @@ } | ||
} | ||
wrapsInPRE() { | ||
let top = this.top | ||
for (let dom = top.contentDOM;; dom = dom.parentNode) { | ||
if (dom.nodeName == "PRE") return true | ||
if (dom == top.dom) return false | ||
} | ||
} | ||
} | ||
@@ -833,3 +917,3 @@ | ||
let child = parent.child(i) | ||
onNode(child, locals, deco.forChild(offset, child)) | ||
onNode(child, locals, deco.forChild(offset, child), i) | ||
offset += child.nodeSize | ||
@@ -870,3 +954,3 @@ } | ||
onNode(child, active.length ? active.slice() : nothing, deco.forChild(offset, child)) | ||
onNode(child, active.length ? active.slice() : nothing, deco.forChild(offset, child), parentIndex - 1) | ||
offset = end | ||
@@ -873,0 +957,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
285697
6659
+ Addedorderedmap@1.1.8(transitive)
+ Addedprosemirror-model@0.16.1(transitive)
+ Addedprosemirror-state@0.16.0(transitive)
+ Addedprosemirror-transform@0.16.0(transitive)
- Removedprosemirror-model@0.15.0(transitive)
- Removedprosemirror-state@0.15.0(transitive)
- Removedprosemirror-transform@0.15.0(transitive)
Updatedprosemirror-model@^0.16.0
Updatedprosemirror-state@^0.16.0