jodit 4.12.16 → 4.12.18

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/es2015/jodit.css +1 -1
  3. package/es2015/jodit.fat.min.js +4 -4
  4. package/es2015/jodit.js +172 -34
  5. package/es2015/jodit.min.js +4 -4
  6. package/es2015/plugins/debug/debug.css +1 -1
  7. package/es2015/plugins/debug/debug.js +1 -1
  8. package/es2015/plugins/debug/debug.min.js +1 -1
  9. package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
  10. package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
  11. package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
  12. package/es2018/jodit.fat.min.js +4 -4
  13. package/es2018/jodit.min.js +4 -4
  14. package/es2018/plugins/debug/debug.min.js +1 -1
  15. package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
  16. package/es2021/jodit.css +1 -1
  17. package/es2021/jodit.fat.min.js +5 -5
  18. package/es2021/jodit.js +170 -34
  19. package/es2021/jodit.min.js +5 -5
  20. package/es2021/plugins/debug/debug.css +1 -1
  21. package/es2021/plugins/debug/debug.js +1 -1
  22. package/es2021/plugins/debug/debug.min.js +1 -1
  23. package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
  24. package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
  25. package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
  26. package/es2021.en/jodit.css +1 -1
  27. package/es2021.en/jodit.fat.min.js +6 -6
  28. package/es2021.en/jodit.js +170 -34
  29. package/es2021.en/jodit.min.js +6 -6
  30. package/es2021.en/plugins/debug/debug.css +1 -1
  31. package/es2021.en/plugins/debug/debug.js +1 -1
  32. package/es2021.en/plugins/debug/debug.min.js +1 -1
  33. package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
  34. package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
  35. package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
  36. package/es5/jodit.css +2 -2
  37. package/es5/jodit.fat.min.js +2 -2
  38. package/es5/jodit.js +185 -34
  39. package/es5/jodit.min.css +2 -2
  40. package/es5/jodit.min.js +2 -2
  41. package/es5/plugins/debug/debug.css +1 -1
  42. package/es5/plugins/debug/debug.js +1 -1
  43. package/es5/plugins/debug/debug.min.js +1 -1
  44. package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
  45. package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
  46. package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
  47. package/es5/polyfills.fat.min.js +1 -1
  48. package/es5/polyfills.js +1 -1
  49. package/es5/polyfills.min.js +1 -1
  50. package/esm/core/constants.js +1 -1
  51. package/esm/core/helpers/utils/config-proto.js +15 -0
  52. package/esm/plugins/clean-html/helpers/visitor/filters/try-remove-node.js +8 -1
  53. package/esm/plugins/drag-and-drop-element/drag-and-drop-element.d.ts +21 -0
  54. package/esm/plugins/drag-and-drop-element/drag-and-drop-element.js +48 -3
  55. package/esm/plugins/enter/enter.js +11 -6
  56. package/esm/plugins/select/select.d.ts +14 -0
  57. package/esm/plugins/select/select.js +69 -0
  58. package/package.json +1 -1
  59. package/types/plugins/drag-and-drop-element/drag-and-drop-element.d.ts +21 -0
  60. package/types/plugins/select/select.d.ts +14 -0
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.16
4
+ * Version: v4.12.18
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.16
4
+ * Version: v4.12.18
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.16
4
+ * Version: v4.12.18
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.16
4
+ * Version: v4.12.18
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.16
4
+ * Version: v4.12.18
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.16
4
+ * Version: v4.12.18
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.16
4
+ * Version: v4.12.18
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
package/es5/polyfills.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.16
4
+ * Version: v4.12.18
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.16
4
+ * Version: v4.12.18
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -3,7 +3,7 @@
3
3
  * Released under MIT see LICENSE.txt in the project root for license information.
4
4
  * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
5
  */
6
- export const APP_VERSION = "4.12.16";
6
+ export const APP_VERSION = "4.12.18";
7
7
  // prettier-ignore
8
8
  export const ES = "es2020";
9
9
  export const IS_ES_MODERN = true;
@@ -10,6 +10,15 @@ import { isVoid } from "../checker/is-void.js";
10
10
  import { Config } from "../../../config.js";
11
11
  import { isAtom } from "./extend.js";
12
12
  import { keys } from "./utils.js";
13
+ /**
14
+ * Keys that must never be copied from a (potentially untrusted) config object —
15
+ * assigning them during a recursive merge can reach and mutate
16
+ * `Object.prototype` (prototype pollution, CWE-1321).
17
+ */
18
+ const UNSAFE_PROTO_KEYS = ['__proto__', 'constructor', 'prototype'];
19
+ function isUnsafeProtoKey(key) {
20
+ return UNSAFE_PROTO_KEYS.indexOf(key) !== -1;
21
+ }
13
22
  /**
14
23
  * @example
15
24
  * ```js
@@ -59,6 +68,9 @@ export function ConfigProto(options, proto, deep = 0) {
59
68
  }
60
69
  const newOpt = {};
61
70
  Object.keys(options).forEach(key => {
71
+ if (isUnsafeProtoKey(key)) {
72
+ return;
73
+ }
62
74
  const opt = options[key], protoKey = proto ? proto[key] : null;
63
75
  if (isPlainObject(opt) && isPlainObject(protoKey) && !isAtom(opt)) {
64
76
  newOpt[key] = ConfigProto(opt, protoKey, deep + 1);
@@ -119,6 +131,9 @@ export function ConfigFlatten(obj) {
119
131
  */
120
132
  export function ConfigMerge(target, source) {
121
133
  Object.keys(source).forEach(key => {
134
+ if (isUnsafeProtoKey(key)) {
135
+ return;
136
+ }
122
137
  const srcVal = source[key];
123
138
  const tgtVal = target[key];
124
139
  if (isPlainObject(srcVal) && isPlainObject(tgtVal) && !isAtom(srcVal)) {
@@ -31,9 +31,16 @@ function isRemovableNode(jodit, node, current, allow, deny) {
31
31
  if (!jodit.o.cleanHTML.removeEmptyElements) {
32
32
  return false;
33
33
  }
34
+ // Never drop an empty inline element that currently holds the caret — it is
35
+ // a pending-format marker the user is about to type into (#1291). `current`
36
+ // is captured before a click moves the caret, so also check the live caret.
37
+ const liveCaret = jodit.s.isCollapsed()
38
+ ? jodit.s.range.startContainer
39
+ : null;
34
40
  return (Dom.isElement(node) &&
35
41
  node.nodeName.match(IS_INLINE) != null &&
36
42
  !Dom.isTemporary(node) &&
37
43
  trimInv(node.innerHTML).length === 0 &&
38
- (current == null || !Dom.isOrContains(node, current)));
44
+ (current == null || !Dom.isOrContains(node, current)) &&
45
+ (liveCaret == null || !Dom.isOrContains(node, liveCaret)));
39
46
  }
@@ -21,10 +21,31 @@ export declare class dragAndDropElement extends Plugin {
21
21
  private state;
22
22
  /** @override */
23
23
  protected afterInit(): void;
24
+ /**
25
+ * Start dragging a specific element programmatically.
26
+ *
27
+ * Allows a separate UI element (for example a drag handle/anchor shown next
28
+ * to a block) to initiate the drag without the user pressing directly on the
29
+ * draggable element itself.
30
+ *
31
+ * @example
32
+ * ```js
33
+ * handle.addEventListener('mousedown', e => {
34
+ * editor.e.fire('startDragElement', preBlock, e);
35
+ * });
36
+ * ```
37
+ */
38
+ private onStartDragElement;
24
39
  /**
25
40
  * Drag start handler
26
41
  */
27
42
  private onDragStart;
43
+ /**
44
+ * Prepare the ghost element and switch to the waiting state.
45
+ * Shared by the native mousedown handler and the programmatic
46
+ * `startDragElement` event handler.
47
+ */
48
+ private startDragging;
28
49
  /**
29
50
  * Mouse move handler handler
30
51
  */
@@ -49,11 +49,34 @@ export class dragAndDropElement extends Plugin {
49
49
  .filter(Boolean)
50
50
  .map(item => item.toLowerCase())
51
51
  : [];
52
+ // Allow another plugin (e.g. a drag handle/anchor) to start dragging
53
+ // an element programmatically, regardless of the `draggableTags` list.
54
+ this.j.e.on('startDragElement', this.onStartDragElement);
52
55
  if (!this.dragList.length) {
53
56
  return;
54
57
  }
55
58
  this.j.e.on('mousedown dragstart', this.onDragStart);
56
59
  }
60
+ /**
61
+ * Start dragging a specific element programmatically.
62
+ *
63
+ * Allows a separate UI element (for example a drag handle/anchor shown next
64
+ * to a block) to initiate the drag without the user pressing directly on the
65
+ * draggable element itself.
66
+ *
67
+ * @example
68
+ * ```js
69
+ * handle.addEventListener('mousedown', e => {
70
+ * editor.e.fire('startDragElement', preBlock, e);
71
+ * });
72
+ * ```
73
+ */
74
+ onStartDragElement(element, event) {
75
+ if (this.isInDestruct || this.state > DragState.IDLE || !element) {
76
+ return;
77
+ }
78
+ this.startDragging(element, event);
79
+ }
57
80
  /**
58
81
  * Drag start handler
59
82
  */
@@ -79,11 +102,19 @@ export class dragAndDropElement extends Plugin {
79
102
  lastTarget.parentElement.lastChild === lastTarget) {
80
103
  lastTarget = lastTarget.parentElement;
81
104
  }
105
+ this.startDragging(lastTarget, event);
106
+ }
107
+ /**
108
+ * Prepare the ghost element and switch to the waiting state.
109
+ * Shared by the native mousedown handler and the programmatic
110
+ * `startDragElement` event handler.
111
+ */
112
+ startDragging(target, event) {
82
113
  this.startX = event.clientX;
83
114
  this.startY = event.clientY;
84
115
  this.isCopyMode = ctrlKey(event); // we can move only element from editor
85
- this.draggable = lastTarget.cloneNode(true);
86
- dataBind(this.draggable, 'target', lastTarget);
116
+ this.draggable = target.cloneNode(true);
117
+ dataBind(this.draggable, 'target', target);
87
118
  this.state = DragState.WAIT_DRAGGING;
88
119
  this.addDragListeners();
89
120
  }
@@ -157,6 +188,15 @@ export class dragAndDropElement extends Plugin {
157
188
  }
158
189
  const { parentElement } = fragment;
159
190
  this.j.s.insertNode(fragment, true, false);
191
+ // Dropping a non-editable block (e.g. a `<pre>` code sample) can leave an
192
+ // invisible filler text node beside it. Remove it so the drop does not
193
+ // introduce a stray empty line (which clean-html would otherwise strip
194
+ // later, causing a flash).
195
+ [fragment.previousSibling, fragment.nextSibling].forEach(node => {
196
+ if (Dom.isEmptyTextNode(node)) {
197
+ Dom.safeRemove(node);
198
+ }
199
+ });
160
200
  if (parentElement &&
161
201
  Dom.isEmpty(parentElement) &&
162
202
  !Dom.isCell(parentElement)) {
@@ -188,10 +228,15 @@ export class dragAndDropElement extends Plugin {
188
228
  /** @override */
189
229
  beforeDestruct() {
190
230
  this.onDragEnd();
191
- this.j.e.off('mousedown dragstart', this.onDragStart);
231
+ this.j.e
232
+ .off('mousedown dragstart', this.onDragStart)
233
+ .off('startDragElement', this.onStartDragElement);
192
234
  this.removeDragListeners();
193
235
  }
194
236
  }
237
+ __decorate([
238
+ autobind
239
+ ], dragAndDropElement.prototype, "onStartDragElement", null);
195
240
  __decorate([
196
241
  autobind
197
242
  ], dragAndDropElement.prototype, "onDragStart", null);
@@ -45,12 +45,17 @@ export class enter extends Plugin {
45
45
  if (beforeEnter !== undefined) {
46
46
  return beforeEnter;
47
47
  }
48
- if (!editor.s.isCollapsed()) {
49
- editor.execCommand('Delete');
50
- }
51
- editor.s.focus();
52
- this.onEnter(event);
53
- editor.e.fire('afterEnter', event);
48
+ // Delete-of-selection + new block must be a single history step,
49
+ // otherwise pressing Enter over a selection needs two Ctrl+Z to undo
50
+ // (the first only reverts to the intermediate empty state). #1292
51
+ editor.history.snapshot.transaction(() => {
52
+ if (!editor.s.isCollapsed()) {
53
+ editor.execCommand('Delete');
54
+ }
55
+ editor.s.focus();
56
+ this.onEnter(event);
57
+ editor.e.fire('afterEnter', event);
58
+ });
54
59
  editor.synchronizeValues(); // fire change
55
60
  return false;
56
61
  }
@@ -33,6 +33,20 @@ export declare class select extends Plugin {
33
33
  protected onOutsideClick(e: MouseEvent): void;
34
34
  protected beforeCommandCut(): void | false;
35
35
  protected beforeCommandSelectAll(): false;
36
+ /**
37
+ * Fix caret position when clicking to the right of a list item that has a
38
+ * nested list. Blink/WebKit place the caret at the start of the line instead
39
+ * of the end (#1296); move it to the end of the item's own text.
40
+ */
41
+ protected onClickRightOfNestedListItem(e: MouseEvent): void;
42
+ /**
43
+ * Keep pending inline formatting after a click. Toggling Bold/Italic/etc. on
44
+ * a collapsed cursor leaves empty marker elements with the caret inside; a
45
+ * click puts the caret right before them (outside), so they get cleaned up
46
+ * and the formatting is lost. Move the caret back into the innermost marker
47
+ * so the next typed character keeps every pending format (#1291).
48
+ */
49
+ protected onClickKeepPendingFormat(): void;
36
50
  /**
37
51
  * Normalize selection after triple click
38
52
  */
@@ -13,6 +13,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
13
13
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14
14
  return c > 3 && r && Object.defineProperty(target, key, r), r;
15
15
  };
16
+ import { IS_INLINE } from "../../core/constants.js";
16
17
  import { autobind, watch } from "../../core/decorators/index.js";
17
18
  import { Dom } from "../../core/dom/dom.js";
18
19
  import { pluginSystem } from "../../core/global.js";
@@ -92,6 +93,68 @@ export class select extends Plugin {
92
93
  s.expandSelection();
93
94
  return false;
94
95
  }
96
+ /**
97
+ * Fix caret position when clicking to the right of a list item that has a
98
+ * nested list. Blink/WebKit place the caret at the start of the line instead
99
+ * of the end (#1296); move it to the end of the item's own text.
100
+ */
101
+ onClickRightOfNestedListItem(e) {
102
+ const { s } = this.j;
103
+ const range = s.range;
104
+ if (!range.collapsed ||
105
+ range.startOffset !== 0 ||
106
+ !Dom.isText(range.startContainer)) {
107
+ return;
108
+ }
109
+ const text = range.startContainer;
110
+ const li = text.parentNode;
111
+ // The text must be the direct content of a list item that has a nested
112
+ // list (the last level has no nested list and behaves correctly).
113
+ if (!Dom.isTag(li, 'li') ||
114
+ !(li.querySelector('ul') || li.querySelector('ol'))) {
115
+ return;
116
+ }
117
+ const measure = this.j.ed.createRange();
118
+ measure.selectNodeContents(text);
119
+ const rect = measure.getBoundingClientRect();
120
+ // Only when the click happened to the right of the text.
121
+ if (e.clientX > rect.right) {
122
+ s.setCursorAfter(text);
123
+ }
124
+ }
125
+ /**
126
+ * Keep pending inline formatting after a click. Toggling Bold/Italic/etc. on
127
+ * a collapsed cursor leaves empty marker elements with the caret inside; a
128
+ * click puts the caret right before them (outside), so they get cleaned up
129
+ * and the formatting is lost. Move the caret back into the innermost marker
130
+ * so the next typed character keeps every pending format (#1291).
131
+ */
132
+ onClickKeepPendingFormat() {
133
+ var _a, _b;
134
+ const { s } = this.j;
135
+ const range = s.range;
136
+ if (!range.collapsed || !Dom.isText(range.startContainer)) {
137
+ return;
138
+ }
139
+ const text = range.startContainer;
140
+ // Caret must sit at the very end of the text, right before the markers.
141
+ if (range.startOffset !== ((_b = (_a = text.nodeValue) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0)) {
142
+ return;
143
+ }
144
+ const marker = text.nextSibling;
145
+ if (!Dom.isElement(marker) ||
146
+ !marker.nodeName.match(IS_INLINE) ||
147
+ !Dom.isEmpty(marker)) {
148
+ return;
149
+ }
150
+ let inner = marker;
151
+ // Descend into the innermost empty formatting marker.
152
+ while (Dom.isElement(inner.firstElementChild) &&
153
+ inner.firstElementChild.nodeName.match(IS_INLINE)) {
154
+ inner = inner.firstElementChild;
155
+ }
156
+ s.setCursorIn(inner);
157
+ }
95
158
  /**
96
159
  * Normalize selection after triple click
97
160
  */
@@ -132,6 +195,12 @@ __decorate([
132
195
  __decorate([
133
196
  watch([':beforeCommandSelectall'])
134
197
  ], select.prototype, "beforeCommandSelectAll", null);
198
+ __decorate([
199
+ watch([':click'])
200
+ ], select.prototype, "onClickRightOfNestedListItem", null);
201
+ __decorate([
202
+ watch([':click'])
203
+ ], select.prototype, "onClickKeepPendingFormat", null);
135
204
  __decorate([
136
205
  watch([':click'])
137
206
  ], select.prototype, "onTripleClickNormalizeSelection", null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jodit",
3
- "version": "4.12.16",
3
+ "version": "4.12.18",
4
4
  "description": "Jodit is an awesome and useful wysiwyg editor with filebrowser",
5
5
  "main": "esm/index.js",
6
6
  "types": "types/index.d.ts",
@@ -21,10 +21,31 @@ export declare class dragAndDropElement extends Plugin {
21
21
  private state;
22
22
  /** @override */
23
23
  protected afterInit(): void;
24
+ /**
25
+ * Start dragging a specific element programmatically.
26
+ *
27
+ * Allows a separate UI element (for example a drag handle/anchor shown next
28
+ * to a block) to initiate the drag without the user pressing directly on the
29
+ * draggable element itself.
30
+ *
31
+ * @example
32
+ * ```js
33
+ * handle.addEventListener('mousedown', e => {
34
+ * editor.e.fire('startDragElement', preBlock, e);
35
+ * });
36
+ * ```
37
+ */
38
+ private onStartDragElement;
24
39
  /**
25
40
  * Drag start handler
26
41
  */
27
42
  private onDragStart;
43
+ /**
44
+ * Prepare the ghost element and switch to the waiting state.
45
+ * Shared by the native mousedown handler and the programmatic
46
+ * `startDragElement` event handler.
47
+ */
48
+ private startDragging;
28
49
  /**
29
50
  * Mouse move handler handler
30
51
  */
@@ -33,6 +33,20 @@ export declare class select extends Plugin {
33
33
  protected onOutsideClick(e: MouseEvent): void;
34
34
  protected beforeCommandCut(): void | false;
35
35
  protected beforeCommandSelectAll(): false;
36
+ /**
37
+ * Fix caret position when clicking to the right of a list item that has a
38
+ * nested list. Blink/WebKit place the caret at the start of the line instead
39
+ * of the end (#1296); move it to the end of the item's own text.
40
+ */
41
+ protected onClickRightOfNestedListItem(e: MouseEvent): void;
42
+ /**
43
+ * Keep pending inline formatting after a click. Toggling Bold/Italic/etc. on
44
+ * a collapsed cursor leaves empty marker elements with the caret inside; a
45
+ * click puts the caret right before them (outside), so they get cleaned up
46
+ * and the formatting is lost. Move the caret back into the innermost marker
47
+ * so the next typed character keeps every pending format (#1291).
48
+ */
49
+ protected onClickKeepPendingFormat(): void;
36
50
  /**
37
51
  * Normalize selection after triple click
38
52
  */