prosemirror-view 1.34.3 → 1.36.0

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 CHANGED
@@ -1,3 +1,19 @@
1
+ ## 1.36.0 (2024-11-05)
2
+
3
+ ### Bug fixes
4
+
5
+ Don't recreated `TrustedTypePolicy` objects for every paste.
6
+
7
+ ### New features
8
+
9
+ Add support for a `destroy` method on custom mark views. Add MarkView to the doc template
10
+
11
+ ## 1.35.0 (2024-10-29)
12
+
13
+ ### New features
14
+
15
+ The new `NodeView.multiType` flag is now required to opt into handling different node types in a single node view. It used to be the default, but this tripped too many people up.
16
+
1
17
  ## 1.34.3 (2024-09-19)
2
18
 
3
19
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -1167,11 +1167,12 @@ var CompositionViewDesc = function (_ViewDesc2) {
1167
1167
  var MarkViewDesc = function (_ViewDesc3) {
1168
1168
  _inherits(MarkViewDesc, _ViewDesc3);
1169
1169
  var _super3 = _createSuper(MarkViewDesc);
1170
- function MarkViewDesc(parent, mark, dom, contentDOM) {
1170
+ function MarkViewDesc(parent, mark, dom, contentDOM, spec) {
1171
1171
  var _this3;
1172
1172
  _classCallCheck(this, MarkViewDesc);
1173
1173
  _this3 = _super3.call(this, parent, [], dom, contentDOM);
1174
1174
  _this3.mark = mark;
1175
+ _this3.spec = spec;
1175
1176
  return _this3;
1176
1177
  }
1177
1178
  _createClass(MarkViewDesc, [{
@@ -1212,13 +1213,19 @@ var MarkViewDesc = function (_ViewDesc3) {
1212
1213
  copy.children = nodes;
1213
1214
  return copy;
1214
1215
  }
1216
+ }, {
1217
+ key: "destroy",
1218
+ value: function destroy() {
1219
+ if (this.spec.destroy) this.spec.destroy();
1220
+ _get(_getPrototypeOf(MarkViewDesc.prototype), "destroy", this).call(this);
1221
+ }
1215
1222
  }], [{
1216
1223
  key: "create",
1217
1224
  value: function create(parent, mark, inline, view) {
1218
1225
  var custom = view.nodeViews[mark.type.name];
1219
1226
  var spec = custom && custom(mark, view, inline);
1220
1227
  if (!spec || !spec.dom) spec = prosemirrorModel.DOMSerializer.renderSpec(document, mark.type.spec.toDOM(mark, inline), null, mark.attrs);
1221
- return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom);
1228
+ return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom, spec);
1222
1229
  }
1223
1230
  }]);
1224
1231
  return MarkViewDesc;
@@ -1564,7 +1571,7 @@ var CustomNodeViewDesc = function (_NodeViewDesc2) {
1564
1571
  key: "update",
1565
1572
  value: function update(node, outerDeco, innerDeco, view) {
1566
1573
  if (this.dirty == NODE_DIRTY) return false;
1567
- if (this.spec.update) {
1574
+ if (this.spec.update && (this.node.type == node.type || this.spec.multiType)) {
1568
1575
  var result = this.spec.update(node, outerDeco, innerDeco);
1569
1576
  if (result) this.updateInner(node, outerDeco, innerDeco, view);
1570
1577
  return result;
@@ -2783,14 +2790,16 @@ var _detachedDoc = null;
2783
2790
  function detachedDoc() {
2784
2791
  return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"));
2785
2792
  }
2793
+ var _policy = null;
2786
2794
  function maybeWrapTrusted(html) {
2787
2795
  var trustedTypes = window.trustedTypes;
2788
2796
  if (!trustedTypes) return html;
2789
- return trustedTypes.createPolicy("detachedDocument", {
2797
+ if (!_policy) _policy = trustedTypes.createPolicy("ProseMirrorClipboard", {
2790
2798
  createHTML: function createHTML(s) {
2791
2799
  return s;
2792
2800
  }
2793
- }).createHTML(html);
2801
+ });
2802
+ return _policy.createHTML(html);
2794
2803
  }
2795
2804
  function readHTML(html) {
2796
2805
  var metas = /^(\s*<meta [^>]*>)*/.exec(html);
@@ -2927,7 +2936,7 @@ editHandlers.keydown = function (view, _event) {
2927
2936
  view.input.lastKeyCode = event.keyCode;
2928
2937
  view.input.lastKeyCodeTime = Date.now();
2929
2938
  if (android && chrome && event.keyCode == 13) return;
2930
- if (view.domObserver.selectionChanged(view.domSelectionRange())) view.domObserver.flush();else if (event.keyCode != 229) view.domObserver.forceFlush();
2939
+ if (event.keyCode != 229) view.domObserver.forceFlush();
2931
2940
  if (ios && event.keyCode == 13 && !event.ctrlKey && !event.altKey && !event.metaKey) {
2932
2941
  var now = Date.now();
2933
2942
  view.input.lastIOSEnter = now;
@@ -4292,11 +4301,6 @@ var DOMObserver = function () {
4292
4301
  }
4293
4302
  return this.queue;
4294
4303
  }
4295
- }, {
4296
- key: "selectionChanged",
4297
- value: function selectionChanged(sel) {
4298
- return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel);
4299
- }
4300
4304
  }, {
4301
4305
  key: "flush",
4302
4306
  value: function flush() {
@@ -4304,8 +4308,8 @@ var DOMObserver = function () {
4304
4308
  if (!view.docView || this.flushingSoon > -1) return;
4305
4309
  var mutations = this.pendingRecords();
4306
4310
  if (mutations.length) this.queue = [];
4307
- var sel = view.domSelectionRange(),
4308
- newSel = this.selectionChanged(sel);
4311
+ var sel = view.domSelectionRange();
4312
+ var newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
4309
4313
  var from = -1,
4310
4314
  to = -1,
4311
4315
  typeOver = false,
package/dist/index.d.cts CHANGED
@@ -245,9 +245,6 @@ node-specific editing interfaces, you want more control over
245
245
  the behavior of a node's in-editor representation, and need to
246
246
  [define](https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews) a custom node view.
247
247
 
248
- Mark views only support `dom` and `contentDOM`, and don't support
249
- any of the node view methods.
250
-
251
248
  Objects returned as node views must conform to this interface.
252
249
  */
253
250
  interface NodeView {
@@ -265,19 +262,28 @@ interface NodeView {
265
262
  */
266
263
  contentDOM?: HTMLElement | null;
267
264
  /**
268
- When given, this will be called when the view is updating itself.
269
- It will be given a node (possibly of a different type), an array
270
- of active decorations around the node (which are automatically
271
- drawn, and the node view may ignore if it isn't interested in
272
- them), and a [decoration source](https://prosemirror.net/docs/ref/#view.DecorationSource) that
273
- represents any decorations that apply to the content of the node
274
- (which again may be ignored). It should return true if it was
275
- able to update to that node, and false otherwise. If the node
276
- view has a `contentDOM` property (or no `dom` property), updating
277
- its child nodes will be handled by ProseMirror.
265
+ When given, this will be called when the view is updating
266
+ itself. It will be given a node, an array of active decorations
267
+ around the node (which are automatically drawn, and the node
268
+ view may ignore if it isn't interested in them), and a
269
+ [decoration source](https://prosemirror.net/docs/ref/#view.DecorationSource) that represents any
270
+ decorations that apply to the content of the node (which again
271
+ may be ignored). It should return true if it was able to update
272
+ to that node, and false otherwise. If the node view has a
273
+ `contentDOM` property (or no `dom` property), updating its child
274
+ nodes will be handled by ProseMirror.
278
275
  */
279
276
  update?: (node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource) => boolean;
280
277
  /**
278
+ By default, `update` will only be called when a node of the same
279
+ node type appears in this view's position. When you set this to
280
+ true, it will be called for any node, making it possible to have
281
+ a node view that representsmultiple types of nodes. You will
282
+ need to check the type of the nodes you get in `update` and
283
+ return `false` for types you cannot handle.
284
+ */
285
+ multiType?: boolean;
286
+ /**
281
287
  Can be used to override the way the node's selected status (as a
282
288
  node selection) is displayed.
283
289
  */
@@ -314,7 +320,32 @@ interface NodeView {
314
320
  ignoreMutation?: (mutation: MutationRecord) => boolean;
315
321
  /**
316
322
  Called when the node view is removed from the editor or the whole
317
- editor is destroyed. (Not available for marks.)
323
+ editor is destroyed.
324
+ */
325
+ destroy?: () => void;
326
+ }
327
+ /**
328
+ By default, document marks are rendered using the result of the
329
+ [`toDOM`](https://prosemirror.net/docs/ref/#model.MarkSpec.toDOM) method of their spec, and managed entirely
330
+ by the editor. For some use cases, you want more control over the behavior
331
+ of a mark's in-editor representation, and need to
332
+ [define](https://prosemirror.net/docs/ref/#view.EditorProps.markViews) a custom mark view.
333
+
334
+ Objects returned as mark views must conform to this interface.
335
+ */
336
+ interface MarkView {
337
+ /**
338
+ The outer DOM node that represents the document node.
339
+ */
340
+ dom: DOMNode;
341
+ /**
342
+ The DOM node that should hold the mark's content. When this is not
343
+ present, the `dom` property is used as the content DOM.
344
+ */
345
+ contentDOM?: HTMLElement | null;
346
+ /**
347
+ Called when the mark view is removed from the editor or the whole
348
+ editor is destroyed.
318
349
  */
319
350
  destroy?: () => void;
320
351
  }
@@ -641,10 +672,7 @@ type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => number |
641
672
  The function types [used](https://prosemirror.net/docs/ref/#view.EditorProps.markViews) to create
642
673
  mark views.
643
674
  */
644
- type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => {
645
- dom: HTMLElement;
646
- contentDOM?: HTMLElement;
647
- };
675
+ type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => MarkView;
648
676
  /**
649
677
  Helper type that maps event names to event object types, but
650
678
  includes events that TypeScript's HTMLElementEventMap doesn't know
@@ -927,4 +955,4 @@ interface DirectEditorProps extends EditorProps {
927
955
  dispatchTransaction?: (tr: Transaction) => void;
928
956
  }
929
957
 
930
- export { type DOMEventMap, Decoration, type DecorationAttrs, DecorationSet, type DecorationSource, type DirectEditorProps, type EditorProps, EditorView, type MarkViewConstructor, type NodeView, type NodeViewConstructor };
958
+ export { type DOMEventMap, Decoration, type DecorationAttrs, DecorationSet, type DecorationSource, type DirectEditorProps, type EditorProps, EditorView, type MarkView, type MarkViewConstructor, type NodeView, type NodeViewConstructor };
package/dist/index.d.ts CHANGED
@@ -245,9 +245,6 @@ node-specific editing interfaces, you want more control over
245
245
  the behavior of a node's in-editor representation, and need to
246
246
  [define](https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews) a custom node view.
247
247
 
248
- Mark views only support `dom` and `contentDOM`, and don't support
249
- any of the node view methods.
250
-
251
248
  Objects returned as node views must conform to this interface.
252
249
  */
253
250
  interface NodeView {
@@ -265,19 +262,28 @@ interface NodeView {
265
262
  */
266
263
  contentDOM?: HTMLElement | null;
267
264
  /**
268
- When given, this will be called when the view is updating itself.
269
- It will be given a node (possibly of a different type), an array
270
- of active decorations around the node (which are automatically
271
- drawn, and the node view may ignore if it isn't interested in
272
- them), and a [decoration source](https://prosemirror.net/docs/ref/#view.DecorationSource) that
273
- represents any decorations that apply to the content of the node
274
- (which again may be ignored). It should return true if it was
275
- able to update to that node, and false otherwise. If the node
276
- view has a `contentDOM` property (or no `dom` property), updating
277
- its child nodes will be handled by ProseMirror.
265
+ When given, this will be called when the view is updating
266
+ itself. It will be given a node, an array of active decorations
267
+ around the node (which are automatically drawn, and the node
268
+ view may ignore if it isn't interested in them), and a
269
+ [decoration source](https://prosemirror.net/docs/ref/#view.DecorationSource) that represents any
270
+ decorations that apply to the content of the node (which again
271
+ may be ignored). It should return true if it was able to update
272
+ to that node, and false otherwise. If the node view has a
273
+ `contentDOM` property (or no `dom` property), updating its child
274
+ nodes will be handled by ProseMirror.
278
275
  */
279
276
  update?: (node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource) => boolean;
280
277
  /**
278
+ By default, `update` will only be called when a node of the same
279
+ node type appears in this view's position. When you set this to
280
+ true, it will be called for any node, making it possible to have
281
+ a node view that representsmultiple types of nodes. You will
282
+ need to check the type of the nodes you get in `update` and
283
+ return `false` for types you cannot handle.
284
+ */
285
+ multiType?: boolean;
286
+ /**
281
287
  Can be used to override the way the node's selected status (as a
282
288
  node selection) is displayed.
283
289
  */
@@ -314,7 +320,32 @@ interface NodeView {
314
320
  ignoreMutation?: (mutation: MutationRecord) => boolean;
315
321
  /**
316
322
  Called when the node view is removed from the editor or the whole
317
- editor is destroyed. (Not available for marks.)
323
+ editor is destroyed.
324
+ */
325
+ destroy?: () => void;
326
+ }
327
+ /**
328
+ By default, document marks are rendered using the result of the
329
+ [`toDOM`](https://prosemirror.net/docs/ref/#model.MarkSpec.toDOM) method of their spec, and managed entirely
330
+ by the editor. For some use cases, you want more control over the behavior
331
+ of a mark's in-editor representation, and need to
332
+ [define](https://prosemirror.net/docs/ref/#view.EditorProps.markViews) a custom mark view.
333
+
334
+ Objects returned as mark views must conform to this interface.
335
+ */
336
+ interface MarkView {
337
+ /**
338
+ The outer DOM node that represents the document node.
339
+ */
340
+ dom: DOMNode;
341
+ /**
342
+ The DOM node that should hold the mark's content. When this is not
343
+ present, the `dom` property is used as the content DOM.
344
+ */
345
+ contentDOM?: HTMLElement | null;
346
+ /**
347
+ Called when the mark view is removed from the editor or the whole
348
+ editor is destroyed.
318
349
  */
319
350
  destroy?: () => void;
320
351
  }
@@ -641,10 +672,7 @@ type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => number |
641
672
  The function types [used](https://prosemirror.net/docs/ref/#view.EditorProps.markViews) to create
642
673
  mark views.
643
674
  */
644
- type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => {
645
- dom: HTMLElement;
646
- contentDOM?: HTMLElement;
647
- };
675
+ type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => MarkView;
648
676
  /**
649
677
  Helper type that maps event names to event object types, but
650
678
  includes events that TypeScript's HTMLElementEventMap doesn't know
@@ -927,4 +955,4 @@ interface DirectEditorProps extends EditorProps {
927
955
  dispatchTransaction?: (tr: Transaction) => void;
928
956
  }
929
957
 
930
- export { type DOMEventMap, Decoration, type DecorationAttrs, DecorationSet, type DecorationSource, type DirectEditorProps, type EditorProps, EditorView, type MarkViewConstructor, type NodeView, type NodeViewConstructor };
958
+ export { type DOMEventMap, Decoration, type DecorationAttrs, DecorationSet, type DecorationSource, type DirectEditorProps, type EditorProps, EditorView, type MarkView, type MarkViewConstructor, type NodeView, type NodeViewConstructor };
package/dist/index.js CHANGED
@@ -1192,16 +1192,17 @@ class CompositionViewDesc extends ViewDesc {
1192
1192
  // some cases they will be split more often than would appear
1193
1193
  // necessary.
1194
1194
  class MarkViewDesc extends ViewDesc {
1195
- constructor(parent, mark, dom, contentDOM) {
1195
+ constructor(parent, mark, dom, contentDOM, spec) {
1196
1196
  super(parent, [], dom, contentDOM);
1197
1197
  this.mark = mark;
1198
+ this.spec = spec;
1198
1199
  }
1199
1200
  static create(parent, mark, inline, view) {
1200
1201
  let custom = view.nodeViews[mark.type.name];
1201
1202
  let spec = custom && custom(mark, view, inline);
1202
1203
  if (!spec || !spec.dom)
1203
1204
  spec = DOMSerializer.renderSpec(document, mark.type.spec.toDOM(mark, inline), null, mark.attrs);
1204
- return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom);
1205
+ return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom, spec);
1205
1206
  }
1206
1207
  parseRule() {
1207
1208
  if ((this.dirty & NODE_DIRTY) || this.mark.type.spec.reparseInView)
@@ -1233,6 +1234,11 @@ class MarkViewDesc extends ViewDesc {
1233
1234
  copy.children = nodes;
1234
1235
  return copy;
1235
1236
  }
1237
+ destroy() {
1238
+ if (this.spec.destroy)
1239
+ this.spec.destroy();
1240
+ super.destroy();
1241
+ }
1236
1242
  }
1237
1243
  // Node view descs are the main, most common type of view desc, and
1238
1244
  // correspond to an actual node in the document. Unlike mark descs,
@@ -1552,7 +1558,7 @@ class CustomNodeViewDesc extends NodeViewDesc {
1552
1558
  update(node, outerDeco, innerDeco, view) {
1553
1559
  if (this.dirty == NODE_DIRTY)
1554
1560
  return false;
1555
- if (this.spec.update) {
1561
+ if (this.spec.update && (this.node.type == node.type || this.spec.multiType)) {
1556
1562
  let result = this.spec.update(node, outerDeco, innerDeco);
1557
1563
  if (result)
1558
1564
  this.updateInner(node, outerDeco, innerDeco, view);
@@ -2953,6 +2959,7 @@ let _detachedDoc = null;
2953
2959
  function detachedDoc() {
2954
2960
  return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"));
2955
2961
  }
2962
+ let _policy = null;
2956
2963
  function maybeWrapTrusted(html) {
2957
2964
  let trustedTypes = window.trustedTypes;
2958
2965
  if (!trustedTypes)
@@ -2960,7 +2967,9 @@ function maybeWrapTrusted(html) {
2960
2967
  // With the require-trusted-types-for CSP, Chrome will block
2961
2968
  // innerHTML, even on a detached document. This wraps the string in
2962
2969
  // a way that makes the browser allow us to use its parser again.
2963
- return trustedTypes.createPolicy("detachedDocument", { createHTML: (s) => s }).createHTML(html);
2970
+ if (!_policy)
2971
+ _policy = trustedTypes.createPolicy("ProseMirrorClipboard", { createHTML: (s) => s });
2972
+ return _policy.createHTML(html);
2964
2973
  }
2965
2974
  function readHTML(html) {
2966
2975
  let metas = /^(\s*<meta [^>]*>)*/.exec(html);
@@ -3111,9 +3120,7 @@ editHandlers.keydown = (view, _event) => {
3111
3120
  // and handling them eagerly tends to corrupt the input.
3112
3121
  if (android && chrome && event.keyCode == 13)
3113
3122
  return;
3114
- if (view.domObserver.selectionChanged(view.domSelectionRange()))
3115
- view.domObserver.flush();
3116
- else if (event.keyCode != 229)
3123
+ if (event.keyCode != 229)
3117
3124
  view.domObserver.forceFlush();
3118
3125
  // On iOS, if we preventDefault enter key presses, the virtual
3119
3126
  // keyboard gets confused. So the hack here is to set a flag that
@@ -4636,9 +4643,6 @@ class DOMObserver {
4636
4643
  this.queue.push(mut);
4637
4644
  return this.queue;
4638
4645
  }
4639
- selectionChanged(sel) {
4640
- return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel);
4641
- }
4642
4646
  flush() {
4643
4647
  let { view } = this;
4644
4648
  if (!view.docView || this.flushingSoon > -1)
@@ -4646,7 +4650,8 @@ class DOMObserver {
4646
4650
  let mutations = this.pendingRecords();
4647
4651
  if (mutations.length)
4648
4652
  this.queue = [];
4649
- let sel = view.domSelectionRange(), newSel = this.selectionChanged(sel);
4653
+ let sel = view.domSelectionRange();
4654
+ let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
4650
4655
  let from = -1, to = -1, typeOver = false, added = [];
4651
4656
  if (view.editable) {
4652
4657
  for (let i = 0; i < mutations.length; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.34.3",
3
+ "version": "1.36.0",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
package/src/README.md CHANGED
@@ -18,6 +18,8 @@ this module.
18
18
 
19
19
  @NodeView
20
20
 
21
+ @MarkView
22
+
21
23
  @DOMEventMap
22
24
 
23
25
  ### Decorations
package/src/clipboard.ts CHANGED
@@ -204,13 +204,16 @@ function detachedDoc() {
204
204
  return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"))
205
205
  }
206
206
 
207
+ let _policy: any = null
208
+
207
209
  function maybeWrapTrusted(html: string): string {
208
210
  let trustedTypes = (window as any).trustedTypes
209
211
  if (!trustedTypes) return html
210
212
  // With the require-trusted-types-for CSP, Chrome will block
211
213
  // innerHTML, even on a detached document. This wraps the string in
212
214
  // a way that makes the browser allow us to use its parser again.
213
- return trustedTypes.createPolicy("detachedDocument", {createHTML: (s: string) => s}).createHTML(html)
215
+ if (!_policy) _policy = trustedTypes.createPolicy("ProseMirrorClipboard", {createHTML: (s: string) => s})
216
+ return _policy.createHTML(html)
214
217
  }
215
218
 
216
219
  function readHTML(html: string) {
@@ -163,17 +163,14 @@ 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
-
170
166
  flush() {
171
167
  let {view} = this
172
168
  if (!view.docView || this.flushingSoon > -1) return
173
169
  let mutations = this.pendingRecords()
174
170
  if (mutations.length) this.queue = []
175
171
 
176
- let sel = view.domSelectionRange(), newSel = this.selectionChanged(sel)
172
+ let sel = view.domSelectionRange()
173
+ let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel)
177
174
 
178
175
  let from = -1, to = -1, typeOver = false, added: Node[] = []
179
176
  if (view.editable) {
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ import {Slice, ResolvedPos, DOMParser, DOMSerializer, Node, Mark} from "prosemir
3
3
 
4
4
  import {scrollRectIntoView, posAtCoords, coordsAtPos, endOfTextblock, storeScrollPos,
5
5
  resetScrollPos, focusPreventScroll} from "./domcoords"
6
- import {docViewDesc, ViewDesc, NodeView, NodeViewDesc} from "./viewdesc"
6
+ import {docViewDesc, ViewDesc, NodeView, NodeViewDesc, MarkView} from "./viewdesc"
7
7
  import {initInput, destroyInput, dispatchEvent, ensureListeners, clearComposition,
8
8
  InputState, doPaste, Dragging, findCompositionNode} from "./input"
9
9
  import {selectionToDOM, anchorInRightPlace, syncNodeSelection} from "./selection"
@@ -14,7 +14,7 @@ import {DOMSelection, DOMNode, DOMSelectionRange, deepActiveElement, clearReused
14
14
  import * as browser from "./browser"
15
15
 
16
16
  export {Decoration, DecorationSet, DecorationAttrs, DecorationSource} from "./decoration"
17
- export {NodeView} from "./viewdesc"
17
+ export {NodeView, MarkView} from "./viewdesc"
18
18
 
19
19
  // Exported for testing
20
20
  import {serializeForClipboard, parseFromClipboard} from "./clipboard"
@@ -576,7 +576,7 @@ export type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => n
576
576
 
577
577
  /// The function types [used](#view.EditorProps.markViews) to create
578
578
  /// mark views.
579
- export type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => {dom: HTMLElement, contentDOM?: HTMLElement}
579
+ export type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => MarkView
580
580
 
581
581
  type NodeViewSet = {[name: string]: NodeViewConstructor | MarkViewConstructor}
582
582
 
package/src/input.ts CHANGED
@@ -112,9 +112,7 @@ 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
-
116
- if (view.domObserver.selectionChanged(view.domSelectionRange())) view.domObserver.flush()
117
- else if (event.keyCode != 229) view.domObserver.forceFlush()
115
+ if (event.keyCode != 229) view.domObserver.forceFlush()
118
116
 
119
117
  // On iOS, if we preventDefault enter key presses, the virtual
120
118
  // keyboard gets confused. So the hack here is to set a flag that
package/src/viewdesc.ts CHANGED
@@ -17,9 +17,6 @@ declare global {
17
17
  /// the behavior of a node's in-editor representation, and need to
18
18
  /// [define](#view.EditorProps.nodeViews) a custom node view.
19
19
  ///
20
- /// Mark views only support `dom` and `contentDOM`, and don't support
21
- /// any of the node view methods.
22
- ///
23
20
  /// Objects returned as node views must conform to this interface.
24
21
  export interface NodeView {
25
22
  /// The outer DOM node that represents the document node.
@@ -33,18 +30,26 @@ export interface NodeView {
33
30
  /// (or deciding not to render) its child nodes.
34
31
  contentDOM?: HTMLElement | null
35
32
 
36
- /// When given, this will be called when the view is updating itself.
37
- /// It will be given a node (possibly of a different type), an array
38
- /// of active decorations around the node (which are automatically
39
- /// drawn, and the node view may ignore if it isn't interested in
40
- /// them), and a [decoration source](#view.DecorationSource) that
41
- /// represents any decorations that apply to the content of the node
42
- /// (which again may be ignored). It should return true if it was
43
- /// able to update to that node, and false otherwise. If the node
44
- /// view has a `contentDOM` property (or no `dom` property), updating
45
- /// its child nodes will be handled by ProseMirror.
33
+ /// When given, this will be called when the view is updating
34
+ /// itself. It will be given a node, an array of active decorations
35
+ /// around the node (which are automatically drawn, and the node
36
+ /// view may ignore if it isn't interested in them), and a
37
+ /// [decoration source](#view.DecorationSource) that represents any
38
+ /// decorations that apply to the content of the node (which again
39
+ /// may be ignored). It should return true if it was able to update
40
+ /// to that node, and false otherwise. If the node view has a
41
+ /// `contentDOM` property (or no `dom` property), updating its child
42
+ /// nodes will be handled by ProseMirror.
46
43
  update?: (node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource) => boolean
47
44
 
45
+ /// By default, `update` will only be called when a node of the same
46
+ /// node type appears in this view's position. When you set this to
47
+ /// true, it will be called for any node, making it possible to have
48
+ /// a node view that representsmultiple types of nodes. You will
49
+ /// need to check the type of the nodes you get in `update` and
50
+ /// return `false` for types you cannot handle.
51
+ multiType?: boolean
52
+
48
53
  /// Can be used to override the way the node's selected status (as a
49
54
  /// node selection) is displayed.
50
55
  selectNode?: () => void
@@ -76,7 +81,27 @@ export interface NodeView {
76
81
  ignoreMutation?: (mutation: MutationRecord) => boolean
77
82
 
78
83
  /// Called when the node view is removed from the editor or the whole
79
- /// editor is destroyed. (Not available for marks.)
84
+ /// editor is destroyed.
85
+ destroy?: () => void
86
+ }
87
+
88
+ /// By default, document marks are rendered using the result of the
89
+ /// [`toDOM`](#model.MarkSpec.toDOM) method of their spec, and managed entirely
90
+ /// by the editor. For some use cases, you want more control over the behavior
91
+ /// of a mark's in-editor representation, and need to
92
+ /// [define](#view.EditorProps.markViews) a custom mark view.
93
+ ///
94
+ /// Objects returned as mark views must conform to this interface.
95
+ export interface MarkView {
96
+ /// The outer DOM node that represents the document node.
97
+ dom: DOMNode
98
+
99
+ /// The DOM node that should hold the mark's content. When this is not
100
+ /// present, the `dom` property is used as the content DOM.
101
+ contentDOM?: HTMLElement | null
102
+
103
+ /// Called when the mark view is removed from the editor or the whole
104
+ /// editor is destroyed.
80
105
  destroy?: () => void
81
106
  }
82
107
 
@@ -564,7 +589,7 @@ class CompositionViewDesc extends ViewDesc {
564
589
  // some cases they will be split more often than would appear
565
590
  // necessary.
566
591
  class MarkViewDesc extends ViewDesc {
567
- constructor(parent: ViewDesc, readonly mark: Mark, dom: DOMNode, contentDOM: HTMLElement) {
592
+ constructor(parent: ViewDesc, readonly mark: Mark, dom: DOMNode, contentDOM: HTMLElement, readonly spec: MarkView) {
568
593
  super(parent, [], dom, contentDOM)
569
594
  }
570
595
 
@@ -573,7 +598,7 @@ class MarkViewDesc extends ViewDesc {
573
598
  let spec: {dom: HTMLElement, contentDOM?: HTMLElement} = custom && (custom as any)(mark, view, inline)
574
599
  if (!spec || !spec.dom)
575
600
  spec = (DOMSerializer.renderSpec as any)(document, mark.type.spec.toDOM!(mark, inline), null, mark.attrs) as any
576
- return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom as HTMLElement)
601
+ return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom as HTMLElement, spec)
577
602
  }
578
603
 
579
604
  parseRule() {
@@ -603,6 +628,11 @@ class MarkViewDesc extends ViewDesc {
603
628
  copy.children = nodes
604
629
  return copy
605
630
  }
631
+
632
+ destroy() {
633
+ if (this.spec.destroy) this.spec.destroy()
634
+ super.destroy()
635
+ }
606
636
  }
607
637
 
608
638
  // Node view descs are the main, most common type of view desc, and
@@ -938,7 +968,7 @@ class CustomNodeViewDesc extends NodeViewDesc {
938
968
  // updates the children.
939
969
  update(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView) {
940
970
  if (this.dirty == NODE_DIRTY) return false
941
- if (this.spec.update) {
971
+ if (this.spec.update && (this.node.type == node.type || this.spec.multiType)) {
942
972
  let result = this.spec.update(node, outerDeco, innerDeco)
943
973
  if (result) this.updateInner(node, outerDeco, innerDeco, view)
944
974
  return result