prosemirror-view 1.34.0 → 1.34.2
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 +16 -0
- package/dist/index.cjs +21 -7
- package/dist/index.js +24 -7
- package/package.json +1 -1
- package/src/clipboard.ts +10 -1
- package/src/dom.ts +5 -2
- package/src/domchange.ts +1 -1
- package/src/domobserver.ts +5 -2
- package/src/input.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## 1.34.2 (2024-09-03)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Fix pasting in Chrome with the require-trusted-types-for CSP enabled.
|
|
6
|
+
|
|
7
|
+
Fix an issue where, for some kinds of IME on Windows, pressing Enter while in a composition would execute the resulting command on the wrong selection, erasing some typed content.
|
|
8
|
+
|
|
9
|
+
## 1.34.1 (2024-08-26)
|
|
10
|
+
|
|
11
|
+
### Bug fixes
|
|
12
|
+
|
|
13
|
+
Fix null dereferences caused by the behavior of Chrome's newly supported `caretPositionFromPoint` method.
|
|
14
|
+
|
|
15
|
+
Fix an issue where on Android, with GBoard, enter presses in code blocks while a spell correction was selected didn't insert a new line.
|
|
16
|
+
|
|
1
17
|
## 1.34.0 (2024-08-23)
|
|
2
18
|
|
|
3
19
|
### New features
|
package/dist/index.cjs
CHANGED
|
@@ -135,7 +135,7 @@ function caretFromPoint(doc, x, y) {
|
|
|
135
135
|
var pos = doc.caretPositionFromPoint(x, y);
|
|
136
136
|
if (pos) return {
|
|
137
137
|
node: pos.offsetNode,
|
|
138
|
-
offset: pos.offset
|
|
138
|
+
offset: Math.min(nodeSize(pos.offsetNode), pos.offset)
|
|
139
139
|
};
|
|
140
140
|
} catch (_) {}
|
|
141
141
|
}
|
|
@@ -143,7 +143,7 @@ function caretFromPoint(doc, x, y) {
|
|
|
143
143
|
var range = doc.caretRangeFromPoint(x, y);
|
|
144
144
|
if (range) return {
|
|
145
145
|
node: range.startContainer,
|
|
146
|
-
offset: range.startOffset
|
|
146
|
+
offset: Math.min(nodeSize(range.startContainer), range.startOffset)
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -2766,6 +2766,15 @@ var _detachedDoc = null;
|
|
|
2766
2766
|
function detachedDoc() {
|
|
2767
2767
|
return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"));
|
|
2768
2768
|
}
|
|
2769
|
+
function maybeWrapTrusted(html) {
|
|
2770
|
+
var trustedTypes = window.trustedTypes;
|
|
2771
|
+
if (!trustedTypes) return html;
|
|
2772
|
+
return trustedTypes.createPolicy("detachedDocument", {
|
|
2773
|
+
createHTML: function createHTML(s) {
|
|
2774
|
+
return s;
|
|
2775
|
+
}
|
|
2776
|
+
}).createHTML(html);
|
|
2777
|
+
}
|
|
2769
2778
|
function readHTML(html) {
|
|
2770
2779
|
var metas = /^(\s*<meta [^>]*>)*/.exec(html);
|
|
2771
2780
|
if (metas) html = html.slice(metas[0].length);
|
|
@@ -2777,7 +2786,7 @@ function readHTML(html) {
|
|
|
2777
2786
|
}).join("") + html + wrap.map(function (n) {
|
|
2778
2787
|
return "</" + n + ">";
|
|
2779
2788
|
}).reverse().join("");
|
|
2780
|
-
elt.innerHTML = html;
|
|
2789
|
+
elt.innerHTML = maybeWrapTrusted(html);
|
|
2781
2790
|
if (wrap) for (var i = 0; i < wrap.length; i++) elt = elt.querySelector(wrap[i]) || elt;
|
|
2782
2791
|
return elt;
|
|
2783
2792
|
}
|
|
@@ -2901,7 +2910,7 @@ editHandlers.keydown = function (view, _event) {
|
|
|
2901
2910
|
view.input.lastKeyCode = event.keyCode;
|
|
2902
2911
|
view.input.lastKeyCodeTime = Date.now();
|
|
2903
2912
|
if (android && chrome && event.keyCode == 13) return;
|
|
2904
|
-
if (event.keyCode != 229) view.domObserver.forceFlush();
|
|
2913
|
+
if (view.domObserver.selectionChanged(view.domSelectionRange())) view.domObserver.flush();else if (event.keyCode != 229) view.domObserver.forceFlush();
|
|
2905
2914
|
if (ios && event.keyCode == 13 && !event.ctrlKey && !event.altKey && !event.metaKey) {
|
|
2906
2915
|
var now = Date.now();
|
|
2907
2916
|
view.input.lastIOSEnter = now;
|
|
@@ -4266,6 +4275,11 @@ var DOMObserver = function () {
|
|
|
4266
4275
|
}
|
|
4267
4276
|
return this.queue;
|
|
4268
4277
|
}
|
|
4278
|
+
}, {
|
|
4279
|
+
key: "selectionChanged",
|
|
4280
|
+
value: function selectionChanged(sel) {
|
|
4281
|
+
return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel);
|
|
4282
|
+
}
|
|
4269
4283
|
}, {
|
|
4270
4284
|
key: "flush",
|
|
4271
4285
|
value: function flush() {
|
|
@@ -4273,8 +4287,8 @@ var DOMObserver = function () {
|
|
|
4273
4287
|
if (!view.docView || this.flushingSoon > -1) return;
|
|
4274
4288
|
var mutations = this.pendingRecords();
|
|
4275
4289
|
if (mutations.length) this.queue = [];
|
|
4276
|
-
var sel = view.domSelectionRange()
|
|
4277
|
-
|
|
4290
|
+
var sel = view.domSelectionRange(),
|
|
4291
|
+
newSel = this.selectionChanged(sel);
|
|
4278
4292
|
var from = -1,
|
|
4279
4293
|
to = -1,
|
|
4280
4294
|
typeOver = false,
|
|
@@ -4560,6 +4574,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4560
4574
|
}
|
|
4561
4575
|
view.input.lastKeyCode = null;
|
|
4562
4576
|
var change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide);
|
|
4577
|
+
if (change) view.input.domChangeCount++;
|
|
4563
4578
|
if ((ios && view.input.lastIOSEnter > Date.now() - 225 || android) && addedNodes.some(function (n) {
|
|
4564
4579
|
return n.nodeType == 1 && !isInline.test(n.nodeName);
|
|
4565
4580
|
}) && (!change || change.endA >= change.endB) && view.someProp("handleKeyDown", function (f) {
|
|
@@ -4587,7 +4602,6 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4587
4602
|
return;
|
|
4588
4603
|
}
|
|
4589
4604
|
}
|
|
4590
|
-
view.input.domChangeCount++;
|
|
4591
4605
|
if (view.state.selection.from < view.state.selection.to && change.start == change.endB && view.state.selection instanceof prosemirrorState.TextSelection) {
|
|
4592
4606
|
if (change.start > view.state.selection.from && change.start <= view.state.selection.from + 2 && view.state.selection.from >= parse.from) {
|
|
4593
4607
|
change.start = view.state.selection.from;
|
package/dist/index.js
CHANGED
|
@@ -139,15 +139,18 @@ function caretFromPoint(doc, x, y) {
|
|
|
139
139
|
if (doc.caretPositionFromPoint) {
|
|
140
140
|
try { // Firefox throws for this call in hard-to-predict circumstances (#994)
|
|
141
141
|
let pos = doc.caretPositionFromPoint(x, y);
|
|
142
|
+
// Clip the offset, because Chrome will return a text offset
|
|
143
|
+
// into <input> nodes, which can't be treated as a regular DOM
|
|
144
|
+
// offset
|
|
142
145
|
if (pos)
|
|
143
|
-
return { node: pos.offsetNode, offset: pos.offset };
|
|
146
|
+
return { node: pos.offsetNode, offset: Math.min(nodeSize(pos.offsetNode), pos.offset) };
|
|
144
147
|
}
|
|
145
148
|
catch (_) { }
|
|
146
149
|
}
|
|
147
150
|
if (doc.caretRangeFromPoint) {
|
|
148
151
|
let range = doc.caretRangeFromPoint(x, y);
|
|
149
152
|
if (range)
|
|
150
|
-
return { node: range.startContainer, offset: range.startOffset };
|
|
153
|
+
return { node: range.startContainer, offset: Math.min(nodeSize(range.startContainer), range.startOffset) };
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
|
|
@@ -2934,6 +2937,15 @@ let _detachedDoc = null;
|
|
|
2934
2937
|
function detachedDoc() {
|
|
2935
2938
|
return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"));
|
|
2936
2939
|
}
|
|
2940
|
+
function maybeWrapTrusted(html) {
|
|
2941
|
+
let trustedTypes = window.trustedTypes;
|
|
2942
|
+
if (!trustedTypes)
|
|
2943
|
+
return html;
|
|
2944
|
+
// With the require-trusted-types-for CSP, Chrome will block
|
|
2945
|
+
// innerHTML, even on a detached document. This wraps the string in
|
|
2946
|
+
// a way that makes the browser allow us to use its parser again.
|
|
2947
|
+
return trustedTypes.createPolicy("detachedDocument", { createHTML: (s) => s }).createHTML(html);
|
|
2948
|
+
}
|
|
2937
2949
|
function readHTML(html) {
|
|
2938
2950
|
let metas = /^(\s*<meta [^>]*>)*/.exec(html);
|
|
2939
2951
|
if (metas)
|
|
@@ -2942,7 +2954,7 @@ function readHTML(html) {
|
|
|
2942
2954
|
let firstTag = /<([a-z][^>\s]+)/i.exec(html), wrap;
|
|
2943
2955
|
if (wrap = firstTag && wrapMap[firstTag[1].toLowerCase()])
|
|
2944
2956
|
html = wrap.map(n => "<" + n + ">").join("") + html + wrap.map(n => "</" + n + ">").reverse().join("");
|
|
2945
|
-
elt.innerHTML = html;
|
|
2957
|
+
elt.innerHTML = maybeWrapTrusted(html);
|
|
2946
2958
|
if (wrap)
|
|
2947
2959
|
for (let i = 0; i < wrap.length; i++)
|
|
2948
2960
|
elt = elt.querySelector(wrap[i]) || elt;
|
|
@@ -3083,7 +3095,9 @@ editHandlers.keydown = (view, _event) => {
|
|
|
3083
3095
|
// and handling them eagerly tends to corrupt the input.
|
|
3084
3096
|
if (android && chrome && event.keyCode == 13)
|
|
3085
3097
|
return;
|
|
3086
|
-
if (
|
|
3098
|
+
if (view.domObserver.selectionChanged(view.domSelectionRange()))
|
|
3099
|
+
view.domObserver.flush();
|
|
3100
|
+
else if (event.keyCode != 229)
|
|
3087
3101
|
view.domObserver.forceFlush();
|
|
3088
3102
|
// On iOS, if we preventDefault enter key presses, the virtual
|
|
3089
3103
|
// keyboard gets confused. So the hack here is to set a flag that
|
|
@@ -4606,6 +4620,9 @@ class DOMObserver {
|
|
|
4606
4620
|
this.queue.push(mut);
|
|
4607
4621
|
return this.queue;
|
|
4608
4622
|
}
|
|
4623
|
+
selectionChanged(sel) {
|
|
4624
|
+
return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel);
|
|
4625
|
+
}
|
|
4609
4626
|
flush() {
|
|
4610
4627
|
let { view } = this;
|
|
4611
4628
|
if (!view.docView || this.flushingSoon > -1)
|
|
@@ -4613,8 +4630,7 @@ class DOMObserver {
|
|
|
4613
4630
|
let mutations = this.pendingRecords();
|
|
4614
4631
|
if (mutations.length)
|
|
4615
4632
|
this.queue = [];
|
|
4616
|
-
let sel = view.domSelectionRange();
|
|
4617
|
-
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
|
|
4633
|
+
let sel = view.domSelectionRange(), newSel = this.selectionChanged(sel);
|
|
4618
4634
|
let from = -1, to = -1, typeOver = false, added = [];
|
|
4619
4635
|
if (view.editable) {
|
|
4620
4636
|
for (let i = 0; i < mutations.length; i++) {
|
|
@@ -4902,6 +4918,8 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4902
4918
|
}
|
|
4903
4919
|
view.input.lastKeyCode = null;
|
|
4904
4920
|
let change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide);
|
|
4921
|
+
if (change)
|
|
4922
|
+
view.input.domChangeCount++;
|
|
4905
4923
|
if ((ios && view.input.lastIOSEnter > Date.now() - 225 || android) &&
|
|
4906
4924
|
addedNodes.some(n => n.nodeType == 1 && !isInline.test(n.nodeName)) &&
|
|
4907
4925
|
(!change || change.endA >= change.endB) &&
|
|
@@ -4927,7 +4945,6 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4927
4945
|
return;
|
|
4928
4946
|
}
|
|
4929
4947
|
}
|
|
4930
|
-
view.input.domChangeCount++;
|
|
4931
4948
|
// Handle the case where overwriting a selection by typing matches
|
|
4932
4949
|
// the start or end of the selected content, creating a change
|
|
4933
4950
|
// that's smaller than what was actually overwritten.
|
package/package.json
CHANGED
package/src/clipboard.ts
CHANGED
|
@@ -204,6 +204,15 @@ function detachedDoc() {
|
|
|
204
204
|
return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"))
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
function maybeWrapTrusted(html: string): string {
|
|
208
|
+
let trustedTypes = (window as any).trustedTypes
|
|
209
|
+
if (!trustedTypes) return html
|
|
210
|
+
// With the require-trusted-types-for CSP, Chrome will block
|
|
211
|
+
// innerHTML, even on a detached document. This wraps the string in
|
|
212
|
+
// a way that makes the browser allow us to use its parser again.
|
|
213
|
+
return trustedTypes.createPolicy("detachedDocument", {createHTML: (s: string) => s}).createHTML(html)
|
|
214
|
+
}
|
|
215
|
+
|
|
207
216
|
function readHTML(html: string) {
|
|
208
217
|
let metas = /^(\s*<meta [^>]*>)*/.exec(html)
|
|
209
218
|
if (metas) html = html.slice(metas[0].length)
|
|
@@ -211,7 +220,7 @@ function readHTML(html: string) {
|
|
|
211
220
|
let firstTag = /<([a-z][^>\s]+)/i.exec(html), wrap
|
|
212
221
|
if (wrap = firstTag && wrapMap[firstTag[1].toLowerCase()])
|
|
213
222
|
html = wrap.map(n => "<" + n + ">").join("") + html + wrap.map(n => "</" + n + ">").reverse().join("")
|
|
214
|
-
elt.innerHTML = html
|
|
223
|
+
elt.innerHTML = maybeWrapTrusted(html)
|
|
215
224
|
if (wrap) for (let i = 0; i < wrap.length; i++) elt = elt.querySelector(wrap[i]) || elt
|
|
216
225
|
return elt
|
|
217
226
|
}
|
package/src/dom.ts
CHANGED
|
@@ -141,11 +141,14 @@ export function caretFromPoint(doc: Document, x: number, y: number): {node: Node
|
|
|
141
141
|
if ((doc as any).caretPositionFromPoint) {
|
|
142
142
|
try { // Firefox throws for this call in hard-to-predict circumstances (#994)
|
|
143
143
|
let pos = (doc as any).caretPositionFromPoint(x, y)
|
|
144
|
-
|
|
144
|
+
// Clip the offset, because Chrome will return a text offset
|
|
145
|
+
// into <input> nodes, which can't be treated as a regular DOM
|
|
146
|
+
// offset
|
|
147
|
+
if (pos) return {node: pos.offsetNode, offset: Math.min(nodeSize(pos.offsetNode), pos.offset)}
|
|
145
148
|
} catch (_) {}
|
|
146
149
|
}
|
|
147
150
|
if (doc.caretRangeFromPoint) {
|
|
148
151
|
let range = doc.caretRangeFromPoint(x, y)
|
|
149
|
-
if (range) return {node: range.startContainer, offset: range.startOffset}
|
|
152
|
+
if (range) return {node: range.startContainer, offset: Math.min(nodeSize(range.startContainer), range.startOffset)}
|
|
150
153
|
}
|
|
151
154
|
}
|
package/src/domchange.ts
CHANGED
|
@@ -120,6 +120,7 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
120
120
|
view.input.lastKeyCode = null
|
|
121
121
|
|
|
122
122
|
let change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide)
|
|
123
|
+
if (change) view.input.domChangeCount++
|
|
123
124
|
if ((browser.ios && view.input.lastIOSEnter > Date.now() - 225 || browser.android) &&
|
|
124
125
|
addedNodes.some(n => n.nodeType == 1 && !isInline.test(n.nodeName)) &&
|
|
125
126
|
(!change || change.endA >= change.endB) &&
|
|
@@ -144,7 +145,6 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
view.input.domChangeCount++
|
|
148
148
|
// Handle the case where overwriting a selection by typing matches
|
|
149
149
|
// the start or end of the selected content, creating a change
|
|
150
150
|
// that's smaller than what was actually overwritten.
|
package/src/domobserver.ts
CHANGED
|
@@ -163,14 +163,17 @@ export class DOMObserver {
|
|
|
163
163
|
return this.queue
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
selectionChanged(sel: DOMSelectionRange) {
|
|
167
|
+
return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel)
|
|
168
|
+
}
|
|
169
|
+
|
|
166
170
|
flush() {
|
|
167
171
|
let {view} = this
|
|
168
172
|
if (!view.docView || this.flushingSoon > -1) return
|
|
169
173
|
let mutations = this.pendingRecords()
|
|
170
174
|
if (mutations.length) this.queue = []
|
|
171
175
|
|
|
172
|
-
let sel = view.domSelectionRange()
|
|
173
|
-
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel)
|
|
176
|
+
let sel = view.domSelectionRange(), newSel = this.selectionChanged(sel)
|
|
174
177
|
|
|
175
178
|
let from = -1, to = -1, typeOver = false, added: Node[] = []
|
|
176
179
|
if (view.editable) {
|
package/src/input.ts
CHANGED
|
@@ -112,7 +112,9 @@ editHandlers.keydown = (view: EditorView, _event: Event) => {
|
|
|
112
112
|
// to be part of a confused sequence of composition events fired,
|
|
113
113
|
// and handling them eagerly tends to corrupt the input.
|
|
114
114
|
if (browser.android && browser.chrome && event.keyCode == 13) return
|
|
115
|
-
|
|
115
|
+
|
|
116
|
+
if (view.domObserver.selectionChanged(view.domSelectionRange())) view.domObserver.flush()
|
|
117
|
+
else if (event.keyCode != 229) view.domObserver.forceFlush()
|
|
116
118
|
|
|
117
119
|
// On iOS, if we preventDefault enter key presses, the virtual
|
|
118
120
|
// keyboard gets confused. So the hack here is to set a flag that
|