prosemirror-view 1.18.7 → 1.18.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/index.es.js +126 -54
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +126 -54
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/clipboard.js +19 -1
- package/src/input.js +22 -19
- package/src/viewdesc.js +88 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prosemirror-view",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.11",
|
|
4
4
|
"description": "ProseMirror's view component",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.es.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"url": "git://github.com/prosemirror/prosemirror-view.git"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"prosemirror-model": "^1.
|
|
21
|
+
"prosemirror-model": "^1.14.3",
|
|
22
22
|
"prosemirror-state": "^1.0.0",
|
|
23
23
|
"prosemirror-transform": "^1.1.0"
|
|
24
24
|
},
|
package/src/clipboard.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {Slice, Fragment, DOMParser, DOMSerializer} from "prosemirror-model"
|
|
2
|
+
import browser from "./browser"
|
|
2
3
|
|
|
3
4
|
export function serializeForClipboard(view, slice) {
|
|
4
5
|
let context = [], {content, openStart, openEnd} = slice
|
|
@@ -46,14 +47,17 @@ export function parseFromClipboard(view, text, html, plainText, $context) {
|
|
|
46
47
|
if (parsed) {
|
|
47
48
|
slice = parsed
|
|
48
49
|
} else {
|
|
50
|
+
let marks = $context.marks()
|
|
51
|
+
let {schema} = view.state, serializer = DOMSerializer.fromSchema(schema)
|
|
49
52
|
dom = document.createElement("div")
|
|
50
53
|
text.trim().split(/(?:\r\n?|\n)+/).forEach(block => {
|
|
51
|
-
dom.appendChild(document.createElement("p")).
|
|
54
|
+
dom.appendChild(document.createElement("p")).appendChild(serializer.serializeNode(schema.text(block, marks)))
|
|
52
55
|
})
|
|
53
56
|
}
|
|
54
57
|
} else {
|
|
55
58
|
view.someProp("transformPastedHTML", f => { html = f(html) })
|
|
56
59
|
dom = readHTML(html)
|
|
60
|
+
if (browser.webkit) restoreReplacedSpaces(dom)
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
let contextNode = dom && dom.querySelector("[data-pm-slice]")
|
|
@@ -178,6 +182,20 @@ function readHTML(html) {
|
|
|
178
182
|
return elt
|
|
179
183
|
}
|
|
180
184
|
|
|
185
|
+
// Webkit browsers do some hard-to-predict replacement of regular
|
|
186
|
+
// spaces with non-breaking spaces when putting content on the
|
|
187
|
+
// clipboard. This tries to convert such non-breaking spaces (which
|
|
188
|
+
// will be wrapped in a plain span on Chrome, a span with class
|
|
189
|
+
// Apple-converted-space on Safari) back to regular spaces.
|
|
190
|
+
function restoreReplacedSpaces(dom) {
|
|
191
|
+
let nodes = dom.querySelectorAll(browser.chrome ? "span:not([class]):not([style])" : "span.Apple-converted-space")
|
|
192
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
193
|
+
let node = nodes[i]
|
|
194
|
+
if (node.childNodes.length == 1 && node.textContent == "\u00a0" && node.parentNode)
|
|
195
|
+
node.parentNode.replaceChild(dom.ownerDocument.createTextNode(" "), node)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
181
199
|
function addContext(slice, context) {
|
|
182
200
|
if (!slice.size) return slice
|
|
183
201
|
let schema = slice.content.firstChild.type.schema, array
|
package/src/input.js
CHANGED
|
@@ -100,7 +100,7 @@ export function dispatchEvent(view, event) {
|
|
|
100
100
|
editHandlers.keydown = (view, event) => {
|
|
101
101
|
view.shiftKey = event.keyCode == 16 || event.shiftKey
|
|
102
102
|
if (inOrNearComposition(view, event)) return
|
|
103
|
-
view.domObserver.forceFlush()
|
|
103
|
+
if (event.keyCode != 229) view.domObserver.forceFlush()
|
|
104
104
|
view.lastKeyCode = event.keyCode
|
|
105
105
|
view.lastKeyCodeTime = Date.now()
|
|
106
106
|
// On iOS, if we preventDefault enter key presses, the virtual
|
|
@@ -220,10 +220,11 @@ function handleDoubleClick(view, pos, inside, event) {
|
|
|
220
220
|
function handleTripleClick(view, pos, inside, event) {
|
|
221
221
|
return runHandlerOnContext(view, "handleTripleClickOn", pos, inside, event) ||
|
|
222
222
|
view.someProp("handleTripleClick", f => f(view, pos, event)) ||
|
|
223
|
-
defaultTripleClick(view, inside)
|
|
223
|
+
defaultTripleClick(view, inside, event)
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
function defaultTripleClick(view, inside) {
|
|
226
|
+
function defaultTripleClick(view, inside, event) {
|
|
227
|
+
if (event.button != 0) return false
|
|
227
228
|
let doc = view.state.doc
|
|
228
229
|
if (inside == -1) {
|
|
229
230
|
if (doc.inlineContent) {
|
|
@@ -303,7 +304,8 @@ class MouseDown {
|
|
|
303
304
|
this.target = targetDesc ? targetDesc.dom : null
|
|
304
305
|
|
|
305
306
|
let {selection} = view.state
|
|
306
|
-
if (
|
|
307
|
+
if (event.button == 0 &&
|
|
308
|
+
targetNode.type.spec.draggable && targetNode.type.spec.selectable !== false ||
|
|
307
309
|
selection instanceof NodeSelection && selection.from <= targetPos && selection.to > targetPos)
|
|
308
310
|
this.mightDrag = {node: targetNode,
|
|
309
311
|
pos: targetPos,
|
|
@@ -350,19 +352,20 @@ class MouseDown {
|
|
|
350
352
|
setSelectionOrigin(this.view, "pointer")
|
|
351
353
|
} else if (handleSingleClick(this.view, pos.pos, pos.inside, event, this.selectNode)) {
|
|
352
354
|
event.preventDefault()
|
|
353
|
-
} else if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
355
|
+
} else if (event.button == 0 &&
|
|
356
|
+
(this.flushed ||
|
|
357
|
+
// Safari ignores clicks on draggable elements
|
|
358
|
+
(browser.safari && this.mightDrag && !this.mightDrag.node.isAtom) ||
|
|
359
|
+
// Chrome will sometimes treat a node selection as a
|
|
360
|
+
// cursor, but still report that the node is selected
|
|
361
|
+
// when asked through getSelection. You'll then get a
|
|
362
|
+
// situation where clicking at the point where that
|
|
363
|
+
// (hidden) cursor is doesn't change the selection, and
|
|
364
|
+
// thus doesn't get a reaction from ProseMirror. This
|
|
365
|
+
// works around that.
|
|
366
|
+
(browser.chrome && !(this.view.state.selection instanceof TextSelection) &&
|
|
367
|
+
Math.min(Math.abs(pos.pos - this.view.state.selection.from),
|
|
368
|
+
Math.abs(pos.pos - this.view.state.selection.to)) <= 2))) {
|
|
366
369
|
updateSelection(this.view, Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer")
|
|
367
370
|
event.preventDefault()
|
|
368
371
|
} else {
|
|
@@ -577,8 +580,8 @@ handlers.dragstart = (view, e) => {
|
|
|
577
580
|
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, mouseDown.mightDrag.pos)))
|
|
578
581
|
} else if (e.target && e.target.nodeType == 1) {
|
|
579
582
|
let desc = view.docView.nearestDesc(e.target, true)
|
|
580
|
-
if (
|
|
581
|
-
|
|
583
|
+
if (desc && desc.node.type.spec.draggable && desc != view.docView)
|
|
584
|
+
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, desc.posBefore)))
|
|
582
585
|
}
|
|
583
586
|
let slice = view.state.selection.content(), {dom, text} = serializeForClipboard(view, slice)
|
|
584
587
|
e.dataTransfer.clearData()
|
package/src/viewdesc.js
CHANGED
|
@@ -113,7 +113,7 @@ class ViewDesc {
|
|
|
113
113
|
matchesWidget() { return false }
|
|
114
114
|
matchesMark() { return false }
|
|
115
115
|
matchesNode() { return false }
|
|
116
|
-
matchesHack() { return false }
|
|
116
|
+
matchesHack(_nodeName) { return false }
|
|
117
117
|
|
|
118
118
|
get beforePosition() { return false }
|
|
119
119
|
|
|
@@ -374,9 +374,17 @@ class ViewDesc {
|
|
|
374
374
|
if (node.nodeType == 3) {
|
|
375
375
|
brKludge = offset && node.nodeValue[offset - 1] == "\n"
|
|
376
376
|
// Issue #1128
|
|
377
|
-
if (brKludge && offset == node.nodeValue.length
|
|
378
|
-
|
|
379
|
-
|
|
377
|
+
if (brKludge && offset == node.nodeValue.length) {
|
|
378
|
+
for (let scan = node, after; scan; scan = scan.parentNode) {
|
|
379
|
+
if (after = scan.nextSibling) {
|
|
380
|
+
if (after.nodeName == "BR")
|
|
381
|
+
anchorDOM = headDOM = {node: after.parentNode, offset: domIndex(after) + 1}
|
|
382
|
+
break
|
|
383
|
+
}
|
|
384
|
+
let desc = scan.pmViewDesc
|
|
385
|
+
if (desc && desc.node && desc.node.isBlock) break
|
|
386
|
+
}
|
|
387
|
+
}
|
|
380
388
|
} else {
|
|
381
389
|
let prev = node.childNodes[offset - 1]
|
|
382
390
|
brKludge = prev && (prev.nodeName == "BR" || prev.contentEditable == "false")
|
|
@@ -445,7 +453,7 @@ class ViewDesc {
|
|
|
445
453
|
else child.markDirty(from - startInside, to - startInside)
|
|
446
454
|
return
|
|
447
455
|
} else {
|
|
448
|
-
child.dirty = NODE_DIRTY
|
|
456
|
+
child.dirty = child.dom == child.contentDOM && child.dom.parentNode == this.contentDOM ? CONTENT_DIRTY : NODE_DIRTY
|
|
449
457
|
}
|
|
450
458
|
}
|
|
451
459
|
offset = end
|
|
@@ -668,8 +676,10 @@ class NodeViewDesc extends ViewDesc {
|
|
|
668
676
|
// `this.children`.
|
|
669
677
|
updateChildren(view, pos) {
|
|
670
678
|
let inline = this.node.inlineContent, off = pos
|
|
671
|
-
let composition =
|
|
672
|
-
let
|
|
679
|
+
let composition = view.composing && this.localCompositionInfo(view, pos)
|
|
680
|
+
let localComposition = composition && composition.pos > -1 ? composition : null
|
|
681
|
+
let compositionInChild = composition && composition.pos < 0
|
|
682
|
+
let updater = new ViewTreeUpdater(this, localComposition && localComposition.node)
|
|
673
683
|
iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
|
|
674
684
|
if (widget.spec.marks)
|
|
675
685
|
updater.syncToMarks(widget.spec.marks, inline, view)
|
|
@@ -681,13 +691,21 @@ class NodeViewDesc extends ViewDesc {
|
|
|
681
691
|
}, (child, outerDeco, innerDeco, i) => {
|
|
682
692
|
// Make sure the wrapping mark descs match the node's marks.
|
|
683
693
|
updater.syncToMarks(child.marks, inline, view)
|
|
684
|
-
//
|
|
685
|
-
|
|
686
|
-
updater.findNodeMatch(child, outerDeco, innerDeco, i)
|
|
687
|
-
//
|
|
688
|
-
|
|
689
|
-
|
|
694
|
+
// Try several strategies for drawing this node
|
|
695
|
+
let compIndex
|
|
696
|
+
if (updater.findNodeMatch(child, outerDeco, innerDeco, i)) {
|
|
697
|
+
// Found precise match with existing node view
|
|
698
|
+
} else if (compositionInChild && view.state.selection.from > off &&
|
|
699
|
+
view.state.selection.to < off + child.nodeSize &&
|
|
700
|
+
(compIndex = updater.findIndexWithChild(composition.node)) > -1 &&
|
|
701
|
+
updater.updateNodeAt(child, outerDeco, innerDeco, compIndex, view)) {
|
|
702
|
+
// Updated the specific node that holds the composition
|
|
703
|
+
} else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i)) {
|
|
704
|
+
// Could update an existing node to reflect this node
|
|
705
|
+
} else {
|
|
706
|
+
// Add it as a new view
|
|
690
707
|
updater.addNode(child, outerDeco, innerDeco, view, off)
|
|
708
|
+
}
|
|
691
709
|
off += child.nodeSize
|
|
692
710
|
})
|
|
693
711
|
// Drop all remaining descs after the current position.
|
|
@@ -698,29 +716,31 @@ class NodeViewDesc extends ViewDesc {
|
|
|
698
716
|
// Sync the DOM if anything changed
|
|
699
717
|
if (updater.changed || this.dirty == CONTENT_DIRTY) {
|
|
700
718
|
// May have to protect focused DOM from being changed if a composition is active
|
|
701
|
-
if (
|
|
719
|
+
if (localComposition) this.protectLocalComposition(view, localComposition)
|
|
702
720
|
renderDescs(this.contentDOM, this.children, view)
|
|
703
721
|
if (browser.ios) iosHacks(this.dom)
|
|
704
722
|
}
|
|
705
723
|
}
|
|
706
724
|
|
|
707
|
-
|
|
725
|
+
localCompositionInfo(view, pos) {
|
|
708
726
|
// Only do something if both the selection and a focused text node
|
|
709
|
-
// are inside of this node
|
|
710
|
-
// view that's a child of this view
|
|
727
|
+
// are inside of this node
|
|
711
728
|
let {from, to} = view.state.selection
|
|
712
729
|
if (!(view.state.selection instanceof TextSelection) || from < pos || to > pos + this.node.content.size) return
|
|
713
730
|
let sel = view.root.getSelection()
|
|
714
731
|
let textNode = nearbyTextNode(sel.focusNode, sel.focusOffset)
|
|
715
732
|
if (!textNode || !this.dom.contains(textNode.parentNode)) return
|
|
716
733
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
734
|
+
if (this.node.inlineContent) {
|
|
735
|
+
// Find the text in the focused node in the node, stop if it's not
|
|
736
|
+
// there (may have been modified through other means, in which
|
|
737
|
+
// case it should overwritten)
|
|
738
|
+
let text = textNode.nodeValue
|
|
739
|
+
let textPos = findTextInFragment(this.node.content, text, from - pos, to - pos)
|
|
740
|
+
return textPos < 0 ? null : {node: textNode, pos: textPos, text}
|
|
741
|
+
} else {
|
|
742
|
+
return {node: textNode, pos: -1}
|
|
743
|
+
}
|
|
724
744
|
}
|
|
725
745
|
|
|
726
746
|
protectLocalComposition(view, {node, pos, text}) {
|
|
@@ -844,6 +864,12 @@ class TextViewDesc extends NodeViewDesc {
|
|
|
844
864
|
return new TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom, view)
|
|
845
865
|
}
|
|
846
866
|
|
|
867
|
+
markDirty(from, to) {
|
|
868
|
+
super.markDirty(from, to)
|
|
869
|
+
if (this.dom != this.nodeDOM && (from == 0 || to == this.nodeDOM.nodeValue.length))
|
|
870
|
+
this.dirty = NODE_DIRTY
|
|
871
|
+
}
|
|
872
|
+
|
|
847
873
|
get domAtom() { return false }
|
|
848
874
|
}
|
|
849
875
|
|
|
@@ -851,7 +877,7 @@ class TextViewDesc extends NodeViewDesc {
|
|
|
851
877
|
// around contentEditable terribleness.
|
|
852
878
|
class TrailingHackViewDesc extends ViewDesc {
|
|
853
879
|
parseRule() { return {ignore: true} }
|
|
854
|
-
matchesHack() { return this.dirty == NOT_DIRTY }
|
|
880
|
+
matchesHack(nodeName) { return this.dirty == NOT_DIRTY && this.dom.nodeName == nodeName }
|
|
855
881
|
get domAtom() { return true }
|
|
856
882
|
}
|
|
857
883
|
|
|
@@ -1132,6 +1158,29 @@ class ViewTreeUpdater {
|
|
|
1132
1158
|
return true
|
|
1133
1159
|
}
|
|
1134
1160
|
|
|
1161
|
+
updateNodeAt(node, outerDeco, innerDeco, index, view) {
|
|
1162
|
+
let child = this.top.children[index]
|
|
1163
|
+
if (!child.update(node, outerDeco, innerDeco, view)) return false
|
|
1164
|
+
this.destroyBetween(this.index, index)
|
|
1165
|
+
this.index = index + 1
|
|
1166
|
+
return true
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
findIndexWithChild(domNode) {
|
|
1170
|
+
for (;;) {
|
|
1171
|
+
let parent = domNode.parentNode
|
|
1172
|
+
if (!parent) return -1
|
|
1173
|
+
if (parent == this.top.contentDOM) {
|
|
1174
|
+
let desc = domNode.pmViewDesc
|
|
1175
|
+
if (desc) for (let i = this.index; i < this.top.children.length; i++) {
|
|
1176
|
+
if (this.top.children[i] == desc) return i
|
|
1177
|
+
}
|
|
1178
|
+
return -1
|
|
1179
|
+
}
|
|
1180
|
+
domNode = parent
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1135
1184
|
// : (Node, [Decoration], DecorationSource, EditorView, Fragment, number) → bool
|
|
1136
1185
|
// Try to update the next node, if any, to the given data. Checks
|
|
1137
1186
|
// pre-matches to avoid overwriting nodes that could still be used.
|
|
@@ -1188,13 +1237,20 @@ class ViewTreeUpdater {
|
|
|
1188
1237
|
if (!lastChild || // Empty textblock
|
|
1189
1238
|
!(lastChild instanceof TextViewDesc) ||
|
|
1190
1239
|
/\n$/.test(lastChild.node.text)) {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1240
|
+
// Avoid a bug in Safari's cursor drawing (#1165)
|
|
1241
|
+
if (browser.safari && lastChild && lastChild.dom.contentEditable == "false")
|
|
1242
|
+
this.addHackNode("IMG")
|
|
1243
|
+
this.addHackNode("BR")
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
addHackNode(nodeName) {
|
|
1248
|
+
if (this.index < this.top.children.length && this.top.children[this.index].matchesHack(nodeName)) {
|
|
1249
|
+
this.index++
|
|
1250
|
+
} else {
|
|
1251
|
+
let dom = document.createElement(nodeName)
|
|
1252
|
+
this.top.children.splice(this.index++, 0, new TrailingHackViewDesc(this.top, nothing, dom, null))
|
|
1253
|
+
this.changed = true
|
|
1198
1254
|
}
|
|
1199
1255
|
}
|
|
1200
1256
|
}
|