prosemirror
Advanced tools
Comparing version 0.9.1 to 0.10.0
@@ -0,1 +1,33 @@ | ||
## 0.10.0 (2016-08-26) | ||
### Bug fixes | ||
Fixed several issues in the handling of clicks on and near leaf nodes. | ||
Fix bug where | ||
[`liftTarget`](http://prosemirror.net/ref.html#liftTarget) would | ||
produce false positives. | ||
Improve support for using ProseMirror in a shadow DOM. | ||
### New features | ||
The | ||
[`ProseMirror.on.domPaste`](http://prosemirror.net/ref.html#ProseMirror.on.domPaste) | ||
event can now be used to entirely override the handling of paste | ||
events. | ||
The new | ||
[`ProseMirror.root`](http://prosemirror.net/ref.html#ProseMirror.root) | ||
property returns the document or shadow DOM root that the editor is | ||
part of. | ||
New method | ||
[`PosMap.forEach`](http://prosemirror.net/ref.html#PosMap.forEach) to | ||
easily iterate over the changed ranges in a position map. | ||
[Marked ranges](http://prosemirror.net/ref.html#ProseMirror.markRange) | ||
now support an `elementBefore` option to insert a DOM node before the | ||
range. | ||
## 0.9.1 (2016-07-29) | ||
@@ -2,0 +34,0 @@ |
@@ -16,2 +16,3 @@ "use strict"; | ||
var TextSelection = _require3.TextSelection; | ||
var isCollapsed = _require3.isCollapsed; | ||
@@ -61,7 +62,7 @@ var _require4 = require("./dompos"); | ||
} | ||
var domSel = window.getSelection(), | ||
var domSel = pm.root.getSelection(), | ||
find = null; | ||
if (domSel.anchorNode && pm.content.contains(domSel.anchorNode)) { | ||
find = [{ node: domSel.anchorNode, offset: domSel.anchorOffset }]; | ||
if (!domSel.isCollapsed) find.push({ node: domSel.focusNode, offset: domSel.focusOffset }); | ||
if (!isCollapsed(domSel)) find.push({ node: domSel.focusNode, offset: domSel.focusOffset }); | ||
} | ||
@@ -68,0 +69,0 @@ var sel = null, |
@@ -180,6 +180,11 @@ "use strict"; | ||
function parentNode(node) { | ||
var parent = node.parentNode; | ||
return parent.nodeType == 11 ? parent.host : parent; | ||
} | ||
function scrollIntoView(pm, pos) { | ||
if (!pos) pos = pm.sel.range.head || pm.sel.range.from; | ||
var coords = coordsAtPos(pm, pos); | ||
for (var parent = pm.content;; parent = parent.parentNode) { | ||
for (var parent = pm.content;; parent = parentNode(parent)) { | ||
var _pm$options = pm.options; | ||
@@ -213,3 +218,3 @@ var scrollThreshold = _pm$options.scrollThreshold; | ||
offset = 0; | ||
for (var child = node.firstChild; child; child = child.nextSibling) { | ||
for (var child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) { | ||
var rects = void 0; | ||
@@ -226,3 +231,3 @@ if (child.nodeType == 1) rects = child.getClientRects();else if (child.nodeType == 3) rects = textRange(child).getClientRects();else continue; | ||
coordsClosest = dx && closest.nodeType == 3 ? { left: rect.right < coords.left ? rect.right : rect.left, top: coords.top } : coords; | ||
if (child.nodeType == 1 && !child.firstChild) offset = i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0); | ||
if (child.nodeType == 1 && dx) offset = childIndex + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0); | ||
continue; | ||
@@ -234,4 +239,4 @@ } | ||
} | ||
if (!closest) return { node: node, offset: offset }; | ||
if (closest.nodeType == 3) return findOffsetInText(closest, coordsClosest); | ||
if (closest && closest.nodeType == 3) return findOffsetInText(closest, coordsClosest); | ||
if (!closest || dxClosest && closest.nodeType == 1) return { node: node, offset: offset }; | ||
return findOffsetInNode(closest, coordsClosest); | ||
@@ -267,3 +272,3 @@ } | ||
function posAtCoords(pm, coords) { | ||
var elt = targetKludge(document.elementFromPoint(coords.left, coords.top + 1), coords); | ||
var elt = targetKludge(pm.root.elementFromPoint(coords.left, coords.top + 1), coords); | ||
if (!contains(pm.content, elt)) return null; | ||
@@ -270,0 +275,0 @@ |
@@ -48,3 +48,2 @@ "use strict"; | ||
if (nextCut > -1) { | ||
if (!fragment) fragment = document.createDocumentFragment(); | ||
size = nextCut - pos; | ||
@@ -59,2 +58,5 @@ nextDOM = splitTextNode(dom, size); | ||
if (ranges.current.length) dom.className = ranges.current.join(" "); | ||
if (!fragment && (nextCut > -1 || ranges.element)) fragment = document.createDocumentFragment(); | ||
if (ranges.element) fragment.appendChild(elt("span", { contenteditable: false, "pm-ignore": true }, ranges.element)); | ||
if (fragment) fragment.appendChild(dom); | ||
@@ -61,0 +63,0 @@ |
@@ -289,3 +289,3 @@ "use strict"; | ||
if (this.mightDrag) { | ||
if (!contains(pm.content, this.target)) this.target = document.elementFromPoint(this.x, this.y); | ||
if (!contains(pm.content, this.target)) this.target = pm.root.elementFromPoint(this.x, this.y); | ||
this.target.draggable = true; | ||
@@ -535,3 +535,3 @@ if (browser.gecko && (this.setContentEditable = !this.target.hasAttribute("contentEditable"))) this.target.setAttribute("contentEditable", "false"); | ||
handlers.paste = function (pm, e) { | ||
if (!hasFocus(pm)) return; | ||
if (!hasFocus(pm) || pm.on.domPaste.dispatch(e)) return; | ||
if (!e.clipboardData) { | ||
@@ -538,0 +538,0 @@ if (browser.ie && browser.ie_version <= 11) readInputSoon(pm); |
@@ -221,2 +221,7 @@ "use strict"; | ||
// :: StoppableSubscription<(DOMEvent)> | ||
// Dispatched when a DOM `paste` event happens on the editor. | ||
// Handlers may declare the event as being handled by calling | ||
// `preventDefault` on it or returning a truthy value. | ||
domPaste: new DOMSubscription(), | ||
// :: StoppableSubscription<(DOMEvent)> | ||
// Dispatched when a DOM `drop` event happens on the editor. | ||
@@ -230,2 +235,4 @@ // Handlers may declare the event as being handled by calling | ||
this._root = null; | ||
this.setDocInner(opts.doc); | ||
@@ -254,4 +261,7 @@ draw(this, this.doc); | ||
// :: (string) → any | ||
// Get the value of the given [option](#edit_options). | ||
// :: DOMDocument | ||
// The root document that the editor is part of. Initialized lazily | ||
// (falling back to the top-level document until the editor is | ||
// placed in the DOM) to make sure asynchronously adding the editor | ||
// to a shadow DOM works correctly. | ||
@@ -261,2 +271,6 @@ | ||
key: "getOption", | ||
// :: (string) → any | ||
// Get the value of the given [option](#edit_options). | ||
value: function getOption(name) { | ||
@@ -444,3 +458,3 @@ return this.options[name]; | ||
if (!document.body.contains(this.wrapper) || !this.operation) return false; | ||
if (!this.root.contains(this.wrapper) || !this.operation) return false; | ||
this.on.flushing.dispatch(); | ||
@@ -609,3 +623,3 @@ | ||
value: function hasFocus() { | ||
if (this.sel.range instanceof NodeSelection) return document.activeElement == this.content;else return _hasFocus(this); | ||
if (this.sel.range instanceof NodeSelection) return this.root.activeElement == this.content;else return _hasFocus(this); | ||
} | ||
@@ -755,2 +769,11 @@ | ||
}, { | ||
key: "root", | ||
get: function get() { | ||
var cached = this._root; | ||
if (cached == null) for (var search = this.wrapper.parentNode; search; search = search.parentNode) { | ||
if (search.nodeType == 9 || search.nodeType == 11 && search.host) return this._root = search; | ||
} | ||
return cached || document; | ||
} | ||
}, { | ||
key: "selection", | ||
@@ -757,0 +780,0 @@ get: function get() { |
@@ -158,4 +158,5 @@ "use strict"; | ||
function significant(range) { | ||
return range.options.className && range.from != range.to; | ||
function significant(obj) { | ||
var range = obj.range; | ||
return range.options.className && range.from != range.to || range.options.elementBefore && obj.type == "open" || range.options.elementAfter && obj.type == "close"; | ||
} | ||
@@ -170,2 +171,3 @@ | ||
this.current = []; | ||
this.element = null; | ||
} | ||
@@ -177,6 +179,11 @@ | ||
var next = void 0; | ||
this.element = null; | ||
while (this.pos < this.sorted.length && (next = this.sorted[this.pos]).at <= pos) { | ||
if (significant(next.range)) { | ||
var className = next.range.options.className; | ||
if (next.type == "open") this.current.push(className);else this.current.splice(this.current.indexOf(className), 1); | ||
if (significant(next)) { | ||
var className = next.range.options.className, | ||
element = next.range.options.elementBefore; | ||
if (className) { | ||
if (next.type == "open") this.current.push(className);else this.current.splice(this.current.indexOf(className), 1); | ||
} | ||
if (element && next.type == "open" && next.at == pos) this.element = element; | ||
} | ||
@@ -192,3 +199,3 @@ this.pos++; | ||
var next = this.sorted[this.pos]; | ||
if (!significant(next.range)) this.pos++;else if (next.at >= pos) return -1;else return next.at; | ||
if (!significant(next)) this.pos++;else if (next.at >= pos) return -1;else return next.at; | ||
} | ||
@@ -195,0 +202,0 @@ } |
@@ -107,3 +107,3 @@ "use strict"; | ||
value: function domChanged() { | ||
var sel = window.getSelection(); | ||
var sel = this.pm.root.getSelection(); | ||
return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || sel.focusNode != this.lastHeadNode || sel.focusOffset != this.lastHeadOffset; | ||
@@ -117,3 +117,3 @@ } | ||
value: function storeDOMState() { | ||
var sel = window.getSelection(); | ||
var sel = this.pm.root.getSelection(); | ||
this.lastAnchorNode = sel.anchorNode;this.lastAnchorOffset = sel.anchorOffset; | ||
@@ -132,3 +132,3 @@ this.lastHeadNode = sel.focusNode;this.lastHeadOffset = sel.focusOffset; | ||
var _selectionFromDOM = selectionFromDOM(this.pm.doc, this.range.head); | ||
var _selectionFromDOM = selectionFromDOM(this.pm, this.range.head); | ||
@@ -172,3 +172,3 @@ var range = _selectionFromDOM.range; | ||
var range = document.createRange(), | ||
sel = window.getSelection(); | ||
sel = this.pm.root.getSelection(); | ||
range.selectNode(dom); | ||
@@ -190,3 +190,3 @@ sel.removeAllRanges(); | ||
var sel = window.getSelection(), | ||
var sel = this.pm.root.getSelection(), | ||
range = document.createRange(); | ||
@@ -461,5 +461,13 @@ if (sel.extend) { | ||
function selectionFromDOM(doc, oldHead) { | ||
var sel = window.getSelection(); | ||
function isCollapsed(sel) { | ||
// Selection.isCollapsed is broken in Chrome 52. | ||
// See https://bugs.chromium.org/p/chromium/issues/detail?id=447523 | ||
return sel.focusNode === sel.anchorNode && sel.focusOffset === sel.anchorOffset; | ||
} | ||
exports.isCollapsed = isCollapsed; | ||
function selectionFromDOM(pm, oldHead) { | ||
var sel = pm.root.getSelection(); | ||
var doc = pm.doc; | ||
var _posFromDOM = posFromDOM(sel.focusNode, sel.focusOffset); | ||
@@ -470,7 +478,8 @@ | ||
if (headLeaf > -1 && sel.isCollapsed) { | ||
var $leaf = doc.resolve(headLeaf); | ||
if ($leaf.nodeAfter.type.selectable) return { range: new NodeSelection($leaf), adjusted: true }; | ||
if (headLeaf > -1 && isCollapsed(sel)) { | ||
var $leaf = doc.resolve(headLeaf), | ||
node = $leaf.nodeAfter; | ||
if (node.type.selectable && !node.type.isInline) return { range: new NodeSelection($leaf), adjusted: true }; | ||
} | ||
var anchor = sel.isCollapsed ? head : posFromDOM(sel.anchorNode, sel.anchorOffset).pos; | ||
var anchor = isCollapsed(sel) ? head : posFromDOM(sel.anchorNode, sel.anchorOffset).pos; | ||
@@ -491,4 +500,4 @@ var range = findSelectionNear(doc.resolve(head), oldHead != null && oldHead < head ? 1 : -1); | ||
function hasFocus(pm) { | ||
if (document.activeElement != pm.content) return false; | ||
var sel = window.getSelection(); | ||
if (pm.root.activeElement != pm.content) return false; | ||
var sel = pm.root.getSelection(); | ||
return sel.rangeCount && contains(pm.content, sel.anchorNode); | ||
@@ -495,0 +504,0 @@ } |
@@ -108,3 +108,3 @@ "use strict"; | ||
value: function selectionCoords() { | ||
var pos = this.config.position == "above" ? topCenterOfSelection() : bottomCenterOfSelection(); | ||
var pos = this.config.position == "above" ? topCenterOfSelection(this.pm.root) : bottomCenterOfSelection(this.pm.root); | ||
if (pos.top != 0) return pos; | ||
@@ -161,4 +161,4 @@ var realPos = this.pm.coordsAtPos(this.pm.selection.from); | ||
function topCenterOfSelection() { | ||
var range = window.getSelection().getRangeAt(0), | ||
function topCenterOfSelection(root) { | ||
var range = root.getSelection().getRangeAt(0), | ||
rects = range.getClientRects(); | ||
@@ -188,4 +188,4 @@ if (!rects.length) return range.getBoundingClientRect(); | ||
function bottomCenterOfSelection() { | ||
var range = window.getSelection().getRangeAt(0), | ||
function bottomCenterOfSelection(root) { | ||
var range = root.getSelection().getRangeAt(0), | ||
rects = range.getClientRects(); | ||
@@ -192,0 +192,0 @@ if (!rects.length) { |
@@ -127,3 +127,3 @@ "use strict"; | ||
if (last.isText && last.sameMarkup(first)) { | ||
content[content.length - 1] = last.copy(last.text + first.text); | ||
content[content.length - 1] = last.withText(last.text + first.text); | ||
i = 1; | ||
@@ -355,3 +355,3 @@ } | ||
if (!joined) joined = array.slice(0, i); | ||
joined[joined.length - 1] = node.copy(joined[joined.length - 1].text + node.text); | ||
joined[joined.length - 1] = node.withText(joined[joined.length - 1].text + node.text); | ||
} else if (joined) { | ||
@@ -358,0 +358,0 @@ joined.push(node); |
@@ -200,3 +200,3 @@ "use strict"; | ||
if (last && last.isText && (m = /\s+$/.exec(last.text))) { | ||
if (last.text.length == m[0].length) this.content.pop();else this.content[this.content.length - 1] = last.copy(last.text.slice(0, last.text.length - m[0].length)); | ||
if (last.text.length == m[0].length) this.content.pop();else this.content[this.content.length - 1] = last.withText(last.text.slice(0, last.text.length - m[0].length)); | ||
} | ||
@@ -472,13 +472,2 @@ } | ||
// : (NodeType, ?Object, [Node]) → ?Node | ||
// Insert a node of the given type, with the given content, based on | ||
// `dom`, at the current position in the document. | ||
}, { | ||
key: "insert", | ||
value: function insert(type, attrs, content) { | ||
var node = type.createAndFill(attrs, content, type.isInline ? this.marks : null); | ||
if (node) this.insertNode(node); | ||
} | ||
// : (NodeType, ?Object) → ?NodeBuilder | ||
@@ -527,6 +516,11 @@ // Try to start a node of the given type, adjusting the context when | ||
value: function normalizeList(dom) { | ||
for (var child = dom.firstChild, prev; child; child = child.nextSibling) { | ||
if (child.nodeType == 1 && listTags.hasOwnProperty(child.nodeName.toLowerCase()) && (prev = child.previousSibling)) { | ||
prev.appendChild(child); | ||
child = prev; | ||
for (var child = dom.firstChild, prevItem = null; child; child = child.nextSibling) { | ||
var name = child.nodeType == 1 ? child.nodeName.toLowerCase() : null; | ||
if (name && listTags.hasOwnProperty(name) && prevItem) { | ||
prevItem.appendChild(child); | ||
child = prevItem; | ||
} else if (name == "li") { | ||
prevItem = child; | ||
} else if (name) { | ||
prevItem = null; | ||
} | ||
@@ -533,0 +527,0 @@ } |
@@ -579,2 +579,8 @@ "use strict"; | ||
}, { | ||
key: "withText", | ||
value: function withText(text) { | ||
if (text == this.text) return this; | ||
return new TextNode(this.type, this.attrs, text, this.marks); | ||
} | ||
}, { | ||
key: "cut", | ||
@@ -586,3 +592,3 @@ value: function cut() { | ||
if (from == 0 && to == this.text.length) return this; | ||
return this.copy(this.text.slice(from, to)); | ||
return this.withText(this.text.slice(from, to)); | ||
} | ||
@@ -589,0 +595,0 @@ }, { |
@@ -180,3 +180,3 @@ "use strict"; | ||
var last = target.length - 1; | ||
if (last >= 0 && child.isText && child.sameMarkup(target[last])) target[last] = child.copy(target[last].text + child.text);else target.push(child); | ||
if (last >= 0 && child.isText && child.sameMarkup(target[last])) target[last] = child.withText(target[last].text + child.text);else target.push(child); | ||
} | ||
@@ -183,0 +183,0 @@ |
@@ -86,3 +86,2 @@ "use strict"; | ||
test(two.parentNode, 1, 11, "force :1 from two"); | ||
test(pm.content, 1, 4, "force :1"); | ||
test(pm.content, 1, 5, "force :1"); | ||
@@ -89,0 +88,0 @@ test(pm.content, 2, 8, "force :2"); |
@@ -157,2 +157,22 @@ "use strict"; | ||
// :: ((oldStart: number, oldEnd: number, newStart: number, newEnd: number)) | ||
// Calls the given function on each of the changed ranges denoted by | ||
// this map. | ||
}, { | ||
key: "forEach", | ||
value: function forEach(f) { | ||
var oldIndex = this.inverted ? 2 : 1, | ||
newIndex = this.inverted ? 1 : 2; | ||
for (var i = 0, diff = 0; i < this.ranges.length; i += 3) { | ||
var start = this.ranges[i], | ||
oldStart = start - (this.inverted ? diff : 0), | ||
newStart = start + (this.inverted ? 0 : diff); | ||
var oldSize = this.ranges[i + oldIndex], | ||
newSize = this.ranges[i + newIndex]; | ||
f(oldStart, oldStart + oldSize, newStart, newStart + newSize); | ||
diff += newSize - oldSize; | ||
} | ||
} | ||
// :: () → PosMap | ||
@@ -159,0 +179,0 @@ // Create an inverted version of this map. The result can be used to |
@@ -19,3 +19,3 @@ "use strict"; | ||
function canCut(node, start, end) { | ||
return (start == 0 || node.canReplace(start, node.childCount)) && (end == node.childCount || node.canReplace(0, start)); | ||
return (start == 0 || node.canReplace(start, node.childCount)) && (end == node.childCount || node.canReplace(0, end)); | ||
} | ||
@@ -227,3 +227,3 @@ | ||
function canJoin(a, b) { | ||
return a && b && !a.isText && a.canAppend(b); | ||
return a && b && !a.isLeaf && a.canAppend(b); | ||
} | ||
@@ -230,0 +230,0 @@ |
{ | ||
"name": "prosemirror", | ||
"version": "0.9.1", | ||
"version": "0.10.0", | ||
"description": "Well-defined WYSIWYG editor", | ||
@@ -5,0 +5,0 @@ "main": "dist/edit/index.js", |
const {Mark} = require("../model") | ||
const {mapThroughResult} = require("../transform") | ||
const {findSelectionFrom, findSelectionNear, TextSelection} = require("./selection") | ||
const {findSelectionFrom, findSelectionNear, TextSelection, isCollapsed} = require("./selection") | ||
const {DOMFromPos, DOMFromPosFromEnd} = require("./dompos") | ||
@@ -38,6 +38,6 @@ | ||
} | ||
let domSel = window.getSelection(), find = null | ||
let domSel = pm.root.getSelection(), find = null | ||
if (domSel.anchorNode && pm.content.contains(domSel.anchorNode)) { | ||
find = [{node: domSel.anchorNode, offset: domSel.anchorOffset}] | ||
if (!domSel.isCollapsed) | ||
if (!isCollapsed(domSel)) | ||
find.push({node: domSel.focusNode, offset: domSel.focusOffset}) | ||
@@ -44,0 +44,0 @@ } |
@@ -167,6 +167,11 @@ const {contains} = require("../util/dom") | ||
function parentNode(node) { | ||
let parent = node.parentNode | ||
return parent.nodeType == 11 ? parent.host : parent | ||
} | ||
function scrollIntoView(pm, pos) { | ||
if (!pos) pos = pm.sel.range.head || pm.sel.range.from | ||
let coords = coordsAtPos(pm, pos) | ||
for (let parent = pm.content;; parent = parent.parentNode) { | ||
for (let parent = pm.content;; parent = parentNode(parent)) { | ||
let {scrollThreshold, scrollMargin} = pm.options | ||
@@ -199,3 +204,3 @@ let atBody = parent == document.body | ||
let closest, dxClosest = 2e8, coordsClosest, offset = 0 | ||
for (let child = node.firstChild; child; child = child.nextSibling) { | ||
for (let child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) { | ||
let rects | ||
@@ -215,4 +220,4 @@ if (child.nodeType == 1) rects = child.getClientRects() | ||
coordsClosest = dx && closest.nodeType == 3 ? {left: rect.right < coords.left ? rect.right : rect.left, top: coords.top} : coords | ||
if (child.nodeType == 1 && !child.firstChild) | ||
offset = i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0) | ||
if (child.nodeType == 1 && dx) | ||
offset = childIndex + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0) | ||
continue | ||
@@ -225,4 +230,4 @@ } | ||
} | ||
if (!closest) return {node, offset} | ||
if (closest.nodeType == 3) return findOffsetInText(closest, coordsClosest) | ||
if (closest && closest.nodeType == 3) return findOffsetInText(closest, coordsClosest) | ||
if (!closest || (dxClosest && closest.nodeType == 1)) return {node, offset} | ||
return findOffsetInNode(closest, coordsClosest) | ||
@@ -260,3 +265,3 @@ } | ||
function posAtCoords(pm, coords) { | ||
let elt = targetKludge(document.elementFromPoint(coords.left, coords.top + 1), coords) | ||
let elt = targetKludge(pm.root.elementFromPoint(coords.left, coords.top + 1), coords) | ||
if (!contains(pm.content, elt)) return null | ||
@@ -263,0 +268,0 @@ |
@@ -38,3 +38,2 @@ const {elt} = require("../util/dom") | ||
if (nextCut > -1) { | ||
if (!fragment) fragment = document.createDocumentFragment() | ||
size = nextCut - pos | ||
@@ -50,4 +49,10 @@ nextDOM = splitTextNode(dom, size) | ||
dom.className = ranges.current.join(" ") | ||
if (fragment) fragment.appendChild(dom) | ||
if (!fragment && (nextCut > -1 || ranges.element)) | ||
fragment = document.createDocumentFragment() | ||
if (ranges.element) | ||
fragment.appendChild(elt("span", {contenteditable: false, "pm-ignore": true}, ranges.element)) | ||
if (fragment) | ||
fragment.appendChild(dom) | ||
if (nextCut == -1) break | ||
@@ -54,0 +59,0 @@ offset += size |
@@ -239,3 +239,3 @@ const Keymap = require("browserkeymap") | ||
if (!contains(pm.content, this.target)) | ||
this.target = document.elementFromPoint(this.x, this.y) | ||
this.target = pm.root.elementFromPoint(this.x, this.y) | ||
this.target.draggable = true | ||
@@ -472,3 +472,3 @@ if (browser.gecko && (this.setContentEditable = !this.target.hasAttribute("contentEditable"))) | ||
handlers.paste = (pm, e) => { | ||
if (!hasFocus(pm)) return | ||
if (!hasFocus(pm) || pm.on.domPaste.dispatch(e)) return | ||
if (!e.clipboardData) { | ||
@@ -475,0 +475,0 @@ if (browser.ie && browser.ie_version <= 11) readInputSoon(pm) |
@@ -153,2 +153,7 @@ require("./css") | ||
// :: StoppableSubscription<(DOMEvent)> | ||
// Dispatched when a DOM `paste` event happens on the editor. | ||
// Handlers may declare the event as being handled by calling | ||
// `preventDefault` on it or returning a truthy value. | ||
domPaste: new DOMSubscription, | ||
// :: StoppableSubscription<(DOMEvent)> | ||
// Dispatched when a DOM `drop` event happens on the editor. | ||
@@ -165,2 +170,4 @@ // Handlers may declare the event as being handled by calling | ||
this._root = null | ||
this.setDocInner(opts.doc) | ||
@@ -188,2 +195,16 @@ draw(this, this.doc) | ||
// :: DOMDocument | ||
// The root document that the editor is part of. Initialized lazily | ||
// (falling back to the top-level document until the editor is | ||
// placed in the DOM) to make sure asynchronously adding the editor | ||
// to a shadow DOM works correctly. | ||
get root() { | ||
let cached = this._root | ||
if (cached == null) for (let search = this.wrapper.parentNode; search; search = search.parentNode) { | ||
if (search.nodeType == 9 || (search.nodeType == 11 && search.host)) | ||
return this._root = search | ||
} | ||
return cached || document | ||
} | ||
// :: (string) → any | ||
@@ -342,3 +363,3 @@ // Get the value of the given [option](#edit_options). | ||
if (!document.body.contains(this.wrapper) || !this.operation) return false | ||
if (!this.root.contains(this.wrapper) || !this.operation) return false | ||
this.on.flushing.dispatch() | ||
@@ -477,3 +498,3 @@ | ||
if (this.sel.range instanceof NodeSelection) | ||
return document.activeElement == this.content | ||
return this.root.activeElement == this.content | ||
else | ||
@@ -480,0 +501,0 @@ return hasFocus(this) |
@@ -123,4 +123,7 @@ // ;; A marked range as created by | ||
function significant(range) { | ||
return range.options.className && range.from != range.to | ||
function significant(obj) { | ||
let range = obj.range | ||
return range.options.className && range.from != range.to || | ||
range.options.elementBefore && obj.type == "open" || | ||
range.options.elementAfter && obj.type == "close" | ||
} | ||
@@ -133,2 +136,3 @@ | ||
this.current = [] | ||
this.element = null | ||
} | ||
@@ -138,9 +142,13 @@ | ||
let next | ||
this.element = null | ||
while (this.pos < this.sorted.length && (next = this.sorted[this.pos]).at <= pos) { | ||
if (significant(next.range)) { | ||
let className = next.range.options.className | ||
if (next.type == "open") | ||
this.current.push(className) | ||
else | ||
this.current.splice(this.current.indexOf(className), 1) | ||
if (significant(next)) { | ||
let className = next.range.options.className, element = next.range.options.elementBefore | ||
if (className) { | ||
if (next.type == "open") | ||
this.current.push(className) | ||
else | ||
this.current.splice(this.current.indexOf(className), 1) | ||
} | ||
if (element && next.type == "open" && next.at == pos) this.element = element | ||
} | ||
@@ -155,3 +163,3 @@ this.pos++ | ||
let next = this.sorted[this.pos] | ||
if (!significant(next.range)) | ||
if (!significant(next)) | ||
this.pos++ | ||
@@ -158,0 +166,0 @@ else if (next.at >= pos) |
@@ -68,3 +68,3 @@ const {contains} = require("../util/dom") | ||
domChanged() { | ||
let sel = window.getSelection() | ||
let sel = this.pm.root.getSelection() | ||
return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || | ||
@@ -76,3 +76,3 @@ sel.focusNode != this.lastHeadNode || sel.focusOffset != this.lastHeadOffset | ||
storeDOMState() { | ||
let sel = window.getSelection() | ||
let sel = this.pm.root.getSelection() | ||
this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset | ||
@@ -88,3 +88,3 @@ this.lastHeadNode = sel.focusNode; this.lastHeadOffset = sel.focusOffset | ||
let {range, adjusted} = selectionFromDOM(this.pm.doc, this.range.head) | ||
let {range, adjusted} = selectionFromDOM(this.pm, this.range.head) | ||
this.setAndSignal(range) | ||
@@ -122,3 +122,3 @@ | ||
} | ||
let range = document.createRange(), sel = window.getSelection() | ||
let range = document.createRange(), sel = this.pm.root.getSelection() | ||
range.selectNode(dom) | ||
@@ -137,3 +137,3 @@ sel.removeAllRanges() | ||
let sel = window.getSelection(), range = document.createRange() | ||
let sel = this.pm.root.getSelection(), range = document.createRange() | ||
if (sel.extend) { | ||
@@ -319,10 +319,18 @@ range.setEnd(anchor.node, anchor.offset) | ||
function selectionFromDOM(doc, oldHead) { | ||
let sel = window.getSelection() | ||
function isCollapsed(sel) { | ||
// Selection.isCollapsed is broken in Chrome 52. | ||
// See https://bugs.chromium.org/p/chromium/issues/detail?id=447523 | ||
return sel.focusNode === sel.anchorNode && sel.focusOffset === sel.anchorOffset | ||
} | ||
exports.isCollapsed = isCollapsed | ||
function selectionFromDOM(pm, oldHead) { | ||
let sel = pm.root.getSelection() | ||
const doc = pm.doc | ||
let {pos: head, inLeaf: headLeaf} = posFromDOM(sel.focusNode, sel.focusOffset) | ||
if (headLeaf > -1 && sel.isCollapsed) { | ||
let $leaf = doc.resolve(headLeaf) | ||
if ($leaf.nodeAfter.type.selectable) return {range: new NodeSelection($leaf), adjusted: true} | ||
if (headLeaf > -1 && isCollapsed(sel)) { | ||
let $leaf = doc.resolve(headLeaf), node = $leaf.nodeAfter | ||
if (node.type.selectable && !node.type.isInline) return {range: new NodeSelection($leaf), adjusted: true} | ||
} | ||
let anchor = sel.isCollapsed ? head : posFromDOM(sel.anchorNode, sel.anchorOffset).pos | ||
let anchor = isCollapsed(sel) ? head : posFromDOM(sel.anchorNode, sel.anchorOffset).pos | ||
@@ -344,4 +352,4 @@ let range = findSelectionNear(doc.resolve(head), oldHead != null && oldHead < head ? 1 : -1) | ||
function hasFocus(pm) { | ||
if (document.activeElement != pm.content) return false | ||
let sel = window.getSelection() | ||
if (pm.root.activeElement != pm.content) return false | ||
let sel = pm.root.getSelection() | ||
return sel.rangeCount && contains(pm.content, sel.anchorNode) | ||
@@ -348,0 +356,0 @@ } |
@@ -73,3 +73,3 @@ const {Plugin} = require("../edit") | ||
selectionCoords() { | ||
let pos = this.config.position == "above" ? topCenterOfSelection() : bottomCenterOfSelection() | ||
let pos = this.config.position == "above" ? topCenterOfSelection(this.pm.root) : bottomCenterOfSelection(this.pm.root) | ||
if (pos.top != 0) return pos | ||
@@ -118,4 +118,4 @@ let realPos = this.pm.coordsAtPos(this.pm.selection.from) | ||
// Get the x and y coordinates at the top center of the current DOM selection. | ||
function topCenterOfSelection() { | ||
let range = window.getSelection().getRangeAt(0), rects = range.getClientRects() | ||
function topCenterOfSelection(root) { | ||
let range = root.getSelection().getRangeAt(0), rects = range.getClientRects() | ||
if (!rects.length) return range.getBoundingClientRect() | ||
@@ -138,4 +138,4 @@ let left, right, top, bottom | ||
function bottomCenterOfSelection() { | ||
let range = window.getSelection().getRangeAt(0), rects = range.getClientRects() | ||
function bottomCenterOfSelection(root) { | ||
let range = root.getSelection().getRangeAt(0), rects = range.getClientRects() | ||
if (!rects.length) { | ||
@@ -142,0 +142,0 @@ let rect = range.getBoundingClientRect() |
@@ -89,3 +89,3 @@ const {fragmentToDOM} = require("./to_dom") | ||
if (last.isText && last.sameMarkup(first)) { | ||
content[content.length - 1] = last.copy(last.text + first.text) | ||
content[content.length - 1] = last.withText(last.text + first.text) | ||
i = 1 | ||
@@ -146,3 +146,3 @@ } | ||
if (!joined) joined = array.slice(0, i) | ||
joined[joined.length - 1] = node.copy(joined[joined.length - 1].text + node.text) | ||
joined[joined.length - 1] = node.withText(joined[joined.length - 1].text + node.text) | ||
} else if (joined) { | ||
@@ -149,0 +149,0 @@ joined.push(node) |
@@ -142,3 +142,3 @@ const {Fragment} = require("./fragment") | ||
if (last.text.length == m[0].length) this.content.pop() | ||
else this.content[this.content.length - 1] = last.copy(last.text.slice(0, last.text.length - m[0].length)) | ||
else this.content[this.content.length - 1] = last.withText(last.text.slice(0, last.text.length - m[0].length)) | ||
} | ||
@@ -375,10 +375,2 @@ } | ||
// : (NodeType, ?Object, [Node]) → ?Node | ||
// Insert a node of the given type, with the given content, based on | ||
// `dom`, at the current position in the document. | ||
insert(type, attrs, content) { | ||
let node = type.createAndFill(attrs, content, type.isInline ? this.marks : null) | ||
if (node) this.insertNode(node) | ||
} | ||
// : (NodeType, ?Object) → ?NodeBuilder | ||
@@ -416,8 +408,11 @@ // Try to start a node of the given type, adjusting the context when | ||
normalizeList(dom) { | ||
for (let child = dom.firstChild, prev; child; child = child.nextSibling) { | ||
if (child.nodeType == 1 && | ||
listTags.hasOwnProperty(child.nodeName.toLowerCase()) && | ||
(prev = child.previousSibling)) { | ||
prev.appendChild(child) | ||
child = prev | ||
for (let child = dom.firstChild, prevItem = null; child; child = child.nextSibling) { | ||
let name = child.nodeType == 1 ? child.nodeName.toLowerCase() : null | ||
if (name && listTags.hasOwnProperty(name) && prevItem) { | ||
prevItem.appendChild(child) | ||
child = prevItem | ||
} else if (name == "li") { | ||
prevItem = child | ||
} else if (name) { | ||
prevItem = null | ||
} | ||
@@ -424,0 +419,0 @@ } |
@@ -365,5 +365,10 @@ const {Fragment} = require("./fragment") | ||
withText(text) { | ||
if (text == this.text) return this | ||
return new TextNode(this.type, this.attrs, text, this.marks) | ||
} | ||
cut(from = 0, to = this.text.length) { | ||
if (from == 0 && to == this.text.length) return this | ||
return this.copy(this.text.slice(from, to)) | ||
return this.withText(this.text.slice(from, to)) | ||
} | ||
@@ -370,0 +375,0 @@ |
@@ -121,3 +121,3 @@ const {ProseMirrorError} = require("../util/error") | ||
if (last >= 0 && child.isText && child.sameMarkup(target[last])) | ||
target[last] = child.copy(target[last].text + child.text) | ||
target[last] = child.withText(target[last].text + child.text) | ||
else | ||
@@ -124,0 +124,0 @@ target.push(child) |
@@ -61,3 +61,2 @@ const {namespace} = require("./def") | ||
test(two.parentNode, 1, 11, "force :1 from two") | ||
test(pm.content, 1, 4, "force :1") | ||
test(pm.content, 1, 5, "force :1") | ||
@@ -64,0 +63,0 @@ test(pm.content, 2, 8, "force :2") |
@@ -112,2 +112,15 @@ // ;; #path=Mappable #kind=interface | ||
// :: ((oldStart: number, oldEnd: number, newStart: number, newEnd: number)) | ||
// Calls the given function on each of the changed ranges denoted by | ||
// this map. | ||
forEach(f) { | ||
let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2 | ||
for (let i = 0, diff = 0; i < this.ranges.length; i += 3) { | ||
let start = this.ranges[i], oldStart = start - (this.inverted ? diff : 0), newStart = start + (this.inverted ? 0 : diff) | ||
let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex] | ||
f(oldStart, oldStart + oldSize, newStart, newStart + newSize) | ||
diff += newSize - oldSize | ||
} | ||
} | ||
// :: () → PosMap | ||
@@ -114,0 +127,0 @@ // Create an inverted version of this map. The result can be used to |
@@ -8,3 +8,3 @@ const {Slice, Fragment} = require("../model") | ||
return (start == 0 || node.canReplace(start, node.childCount)) && | ||
(end == node.childCount || node.canReplace(0, start)) | ||
(end == node.childCount || node.canReplace(0, end)) | ||
} | ||
@@ -195,3 +195,3 @@ | ||
function canJoin(a, b) { | ||
return a && b && !a.isText && a.canAppend(b) | ||
return a && b && !a.isLeaf && a.canAppend(b) | ||
} | ||
@@ -198,0 +198,0 @@ |
1308150
31051