jodit 4.18.2 → 4.18.3

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 (95) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/es2015/jodit.css +7 -6
  3. package/es2015/jodit.fat.min.js +10 -10
  4. package/es2015/jodit.js +475 -183
  5. package/es2015/jodit.min.js +10 -10
  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 +9 -9
  13. package/es2018/jodit.min.js +9 -9
  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 +7 -6
  17. package/es2021/jodit.fat.min.js +16 -16
  18. package/es2021/jodit.js +415 -129
  19. package/es2021/jodit.min.js +16 -16
  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 +7 -6
  27. package/es2021.en/jodit.fat.min.js +25 -25
  28. package/es2021.en/jodit.js +415 -129
  29. package/es2021.en/jodit.min.js +16 -16
  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 +8 -7
  37. package/es5/jodit.fat.min.js +2 -2
  38. package/es5/jodit.js +536 -209
  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/config.d.ts +4 -2
  51. package/esm/core/constants.js +1 -1
  52. package/esm/core/helpers/utils/index.d.ts +1 -0
  53. package/esm/core/helpers/utils/index.js +1 -0
  54. package/esm/core/helpers/utils/prepare-media.d.ts +19 -0
  55. package/esm/core/helpers/utils/prepare-media.js +31 -0
  56. package/esm/core/selection/style/api/wrap-unwrapped-text.js +3 -2
  57. package/esm/modules/history/history.d.ts +17 -2
  58. package/esm/modules/history/history.js +41 -19
  59. package/esm/modules/table/table.js +7 -2
  60. package/esm/modules/widget/file-selector/file-selector.js +2 -2
  61. package/esm/plugins/backspace/backspace.js +4 -0
  62. package/esm/plugins/clean-html/clean-html.d.ts +7 -0
  63. package/esm/plugins/clean-html/clean-html.js +17 -0
  64. package/esm/plugins/clean-html/helpers/visitor/filters/replace-old-tags.js +26 -1
  65. package/esm/plugins/dtd/before-insert/check-block-nesting.d.ts +2 -1
  66. package/esm/plugins/dtd/before-insert/check-block-nesting.js +26 -5
  67. package/esm/plugins/enter/helpers/move-cursor-out-from-specal-tags.js +8 -0
  68. package/esm/plugins/focus/focus.js +3 -0
  69. package/esm/plugins/hr/hr.js +21 -5
  70. package/esm/plugins/iframe/config.js +8 -3
  71. package/esm/plugins/inline-popup/inline-popup.d.ts +5 -0
  72. package/esm/plugins/inline-popup/inline-popup.js +26 -2
  73. package/esm/plugins/link/link.js +5 -2
  74. package/esm/plugins/media/config.d.ts +2 -1
  75. package/esm/plugins/media/media.js +21 -2
  76. package/esm/plugins/paste/helpers.d.ts +8 -0
  77. package/esm/plugins/paste/helpers.js +18 -1
  78. package/esm/plugins/paste/paste.js +2 -2
  79. package/esm/plugins/resizer/resizer.js +8 -3
  80. package/esm/plugins/select-cells/select-cells.d.ts +10 -0
  81. package/esm/plugins/select-cells/select-cells.js +54 -6
  82. package/esm/plugins/table/config.js +51 -1
  83. package/esm/plugins/table-keyboard-navigation/table-keyboard-navigation.js +1 -1
  84. package/esm/plugins/video/config.js +6 -22
  85. package/package.json +1 -1
  86. package/types/config.d.ts +4 -2
  87. package/types/core/helpers/utils/index.d.ts +1 -0
  88. package/types/core/helpers/utils/prepare-media.d.ts +19 -0
  89. package/types/modules/history/history.d.ts +17 -2
  90. package/types/plugins/clean-html/clean-html.d.ts +7 -0
  91. package/types/plugins/dtd/before-insert/check-block-nesting.d.ts +2 -1
  92. package/types/plugins/inline-popup/inline-popup.d.ts +5 -0
  93. package/types/plugins/media/config.d.ts +2 -1
  94. package/types/plugins/paste/helpers.d.ts +8 -0
  95. package/types/plugins/select-cells/select-cells.d.ts +10 -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.18.2
4
+ * Version: v4.18.3
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.18.2
4
+ * Version: v4.18.3
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.18.2
4
+ * Version: v4.18.3
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.18.2
4
+ * Version: v4.18.3
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.18.2
4
+ * Version: v4.18.3
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.18.2
4
+ * Version: v4.18.3
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.18.2
4
+ * Version: v4.18.3
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.18.2
4
+ * Version: v4.18.3
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.18.2
4
+ * Version: v4.18.3
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
package/esm/config.d.ts CHANGED
@@ -989,7 +989,8 @@ interface Config {
989
989
  */
990
990
  maxHistoryLength: number;
991
991
  /**
992
- * Delay on every change
992
+ * How long a step stays open after its first change, in
993
+ * milliseconds. The changes made in that time are undone together.
993
994
  */
994
995
  timeout: number;
995
996
  };
@@ -1597,7 +1598,8 @@ interface Config {
1597
1598
  * - is wrapped in it instead of the editor-only `mediaFakeTag` when its tag
1598
1599
  * is one of `mediaBlocks`, or left in it when it's already in one
1599
1600
  * - goes in inside it when the video popup inserts it, as an upload, a
1600
- * link's embed or embed code that is one element
1601
+ * link's embed or embed code that is one element, and when a pasted
1602
+ * video link is turned into its embed
1601
1603
  * - opens the media popup for its wrapper when clicked, whose Align and
1602
1604
  * Delete act on the wrapper
1603
1605
  *
@@ -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.18.2";
6
+ export const APP_VERSION = "4.18.3";
7
7
  // prettier-ignore
8
8
  export const ES = "es2020";
9
9
  export const IS_ES_MODERN = true;
@@ -28,6 +28,7 @@ export * from "./is-unsafe-proto-key";
28
28
  export * from "./mark-deprecated";
29
29
  export * from "./media-wrapper";
30
30
  export * from "./parse-query";
31
+ export * from "./prepare-media";
31
32
  export * from "./print";
32
33
  export * from "./reset";
33
34
  export * from "./scroll-into-view";
@@ -28,6 +28,7 @@ export * from "./is-unsafe-proto-key.js";
28
28
  export * from "./mark-deprecated.js";
29
29
  export * from "./media-wrapper.js";
30
30
  export * from "./parse-query.js";
31
+ export * from "./prepare-media.js";
31
32
  export * from "./print.js";
32
33
  export * from "./reset.js";
33
34
  export * from "./scroll-into-view.js";
@@ -0,0 +1,19 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ /**
7
+ * @module helpers/utils
8
+ */
9
+ import type { IJodit } from "../../../types/index";
10
+ /**
11
+ * Puts new media in its wrapper (see `mediaWrappers`) and aligns it with
12
+ * `mediaDefaultAlign`. Returns the wrapper, or `element` when there is none
13
+ */
14
+ export declare function prepareMedia(jodit: IJodit, element: HTMLElement): HTMLElement;
15
+ /**
16
+ * Embed code that is one element is prepared as media (see `prepareMedia`);
17
+ * other code is returned as it is
18
+ */
19
+ export declare function prepareEmbedCode(jodit: IJodit, code: string): string | HTMLElement;
@@ -0,0 +1,31 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ import { getAlignClasses, hAlignElement } from "./align.js";
7
+ import { wrapMedia } from "./media-wrapper.js";
8
+ /**
9
+ * Puts new media in its wrapper (see `mediaWrappers`) and aligns it with
10
+ * `mediaDefaultAlign`. Returns the wrapper, or `element` when there is none
11
+ */
12
+ export function prepareMedia(jodit, element) {
13
+ const media = wrapMedia(jodit, element);
14
+ const { mediaDefaultAlign } = jodit.o;
15
+ if (mediaDefaultAlign && mediaDefaultAlign !== 'normal') {
16
+ hAlignElement(media, mediaDefaultAlign, getAlignClasses(jodit, media));
17
+ }
18
+ return media;
19
+ }
20
+ /**
21
+ * Embed code that is one element is prepared as media (see `prepareMedia`);
22
+ * other code is returned as it is
23
+ */
24
+ export function prepareEmbedCode(jodit, code) {
25
+ const box = jodit.createInside.div();
26
+ box.innerHTML = code.trim();
27
+ const element = box.firstElementChild;
28
+ return element && box.childNodes.length === 1
29
+ ? prepareMedia(jodit, element)
30
+ : code;
31
+ }
@@ -43,8 +43,9 @@ export function wrapUnwrappedText(style, elm, jodit) {
43
43
  Dom.append(wrapper, fragment);
44
44
  Dom.safeInsertNode(range, wrapper);
45
45
  if (style.elementIsBlock) {
46
- if (Dom.isEmpty(wrapper) &&
47
- !Dom.isTag(wrapper.firstElementChild, 'br')) {
46
+ // The wrapped content can bring its own `<br>` (an empty table
47
+ // cell's), after or inside the temporary `<font>` around the selection
48
+ if (Dom.isEmpty(wrapper) && !wrapper.querySelector('br')) {
48
49
  Dom.append(wrapper, ci.element('br'));
49
50
  }
50
51
  }
@@ -21,7 +21,8 @@ declare module 'jodit/config' {
21
21
  */
22
22
  maxHistoryLength: number;
23
23
  /**
24
- * Delay on every change
24
+ * How long a step stays open after its first change, in
25
+ * milliseconds. The changes made in that time are undone together.
25
26
  */
26
27
  timeout: number;
27
28
  };
@@ -52,15 +53,29 @@ export declare class History extends ViewComponent<IJodit> implements IHistory {
52
53
  snapshot: ISnapshot & IDestructible;
53
54
  constructor(editor: IJodit, stack?: Stack, snapshot?: Snapshot);
54
55
  private updateTick;
56
+ /**
57
+ * A step is open: changes have come in that are not in the stack yet
58
+ */
59
+ private __hasPendingChanges;
60
+ /**
61
+ * Records the open step `history.timeout` after its first change
62
+ */
63
+ private __timer;
55
64
  /**
56
65
  * Update change counter
57
66
  * @internal
58
67
  */
59
68
  protected __upTick(): void;
60
69
  /**
61
- * Push new command in stack on some changes
70
+ * Push new command in stack on some changes. The first change opens a
71
+ * step, and later ones join it until `history.timeout` has passed,
72
+ * however close together they come.
62
73
  */
63
74
  private onChange;
75
+ /**
76
+ * Push the open step's changes now
77
+ */
78
+ private __processPendingChanges;
64
79
  /**
65
80
  * @internal
66
81
  */
@@ -3,18 +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
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
7
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
8
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
9
- r = Reflect.decorate(decorators, target, key, desc);
10
- else
11
- for (var i = decorators.length - 1; i >= 0; i--)
12
- if (d = decorators[i])
13
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14
- return c > 3 && r && Object.defineProperty(target, key, r), r;
15
- };
16
6
  import { ViewComponent } from "../../core/component/view-component.js";
17
- import { debounce } from "../../core/decorators/debounce/debounce.js";
18
7
  import { Config } from "../../config.js";
19
8
  import { Command } from "./command.js";
20
9
  import { Snapshot } from "./snapshot.js";
@@ -36,28 +25,32 @@ export class History extends ViewComponent {
36
25
  * Return state of the WYSIWYG editor to step back
37
26
  */
38
27
  redo() {
28
+ this.__processPendingChanges();
39
29
  if (this.__stack.redo()) {
40
30
  this.startValue = this.snapshot.make();
41
31
  this.fireChangeStack();
42
32
  }
43
33
  }
44
34
  canRedo() {
45
- return this.__stack.canRedo();
35
+ return !this.__hasPendingChanges && this.__stack.canRedo();
46
36
  }
47
37
  /**
48
38
  * Return the state of the WYSIWYG editor to step forward
49
39
  */
50
40
  undo() {
41
+ this.__processPendingChanges();
51
42
  if (this.__stack.undo()) {
52
43
  this.startValue = this.snapshot.make();
53
44
  this.fireChangeStack();
54
45
  }
55
46
  }
56
47
  canUndo() {
57
- return this.__stack.canUndo();
48
+ return this.__hasPendingChanges || this.__stack.canUndo();
58
49
  }
59
50
  clear() {
60
51
  this.startValue = this.snapshot.make();
52
+ this.async.clearTimeout(this.__timer);
53
+ this.__hasPendingChanges = false;
61
54
  this.__stack.clear();
62
55
  this.fireChangeStack();
63
56
  }
@@ -73,6 +66,14 @@ export class History extends ViewComponent {
73
66
  constructor(editor, stack = new Stack(editor.o.history.maxHistoryLength), snapshot = new Snapshot(editor)) {
74
67
  super(editor);
75
68
  this.updateTick = 0;
69
+ /**
70
+ * A step is open: changes have come in that are not in the stack yet
71
+ */
72
+ this.__hasPendingChanges = false;
73
+ /**
74
+ * Records the open step `history.timeout` after its first change
75
+ */
76
+ this.__timer = 0;
76
77
  this.__stack = stack;
77
78
  this.snapshot = snapshot;
78
79
  if (editor.o.history.enable) {
@@ -84,6 +85,7 @@ export class History extends ViewComponent {
84
85
  editor.events
85
86
  // save selection
86
87
  .on('internalChange internalUpdate', () => {
88
+ this.__processPendingChanges();
87
89
  this.startValue = this.snapshot.make();
88
90
  })
89
91
  .on(editor.editor, [
@@ -102,7 +104,7 @@ export class History extends ViewComponent {
102
104
  this.startValue = this.snapshot.make();
103
105
  }
104
106
  })
105
- .on(this, 'change.history', this.onChange);
107
+ .on(this, 'change.history', () => this.onChange());
106
108
  });
107
109
  }
108
110
  }
@@ -114,10 +116,31 @@ export class History extends ViewComponent {
114
116
  this.updateTick += 1;
115
117
  }
116
118
  /**
117
- * Push new command in stack on some changes
119
+ * Push new command in stack on some changes. The first change opens a
120
+ * step, and later ones join it until `history.timeout` has passed,
121
+ * however close together they come.
118
122
  */
119
123
  onChange() {
120
- this.__processChanges();
124
+ // A transaction is over by the time the step is recorded, so it is
125
+ // checked now
126
+ if (this.snapshot.isBlocked || this.__hasPendingChanges) {
127
+ return;
128
+ }
129
+ this.__hasPendingChanges = true;
130
+ this.__timer = this.async.setTimeout(() => this.__processPendingChanges(), this.j.o.history.timeout);
131
+ // With a timeout of 0 the step is already recorded. Otherwise
132
+ // canUndo and canRedo count it from now
133
+ if (this.__hasPendingChanges) {
134
+ this.fireChangeStack();
135
+ }
136
+ }
137
+ /**
138
+ * Push the open step's changes now
139
+ */
140
+ __processPendingChanges() {
141
+ if (this.__hasPendingChanges) {
142
+ this.__processChanges();
143
+ }
121
144
  }
122
145
  /**
123
146
  * @internal
@@ -126,6 +149,8 @@ export class History extends ViewComponent {
126
149
  if (this.snapshot.isBlocked || !this.j.o.history.enable) {
127
150
  return;
128
151
  }
152
+ this.async.clearTimeout(this.__timer);
153
+ this.__hasPendingChanges = false;
129
154
  this.updateStack();
130
155
  }
131
156
  /**
@@ -171,6 +196,3 @@ export class History extends ViewComponent {
171
196
  super.destruct();
172
197
  }
173
198
  }
174
- __decorate([
175
- debounce()
176
- ], History.prototype, "onChange", null);
@@ -172,7 +172,9 @@ export class Table extends ViewComponent {
172
172
  const columnsCount = Table.__getColumnsCount(table);
173
173
  row = create.element('tr');
174
174
  for (let j = 0; j < columnsCount; j += 1) {
175
- Dom.append(row, create.element('td'));
175
+ const cell = create.element('td');
176
+ Dom.append(cell, create.element('br'));
177
+ Dom.append(row, cell);
176
178
  }
177
179
  }
178
180
  else {
@@ -190,6 +192,7 @@ export class Table extends ViewComponent {
190
192
  Dom.each(row, cell => {
191
193
  if (Dom.isCell(cell)) {
192
194
  Dom.detach(cell);
195
+ Dom.append(cell, create.element('br'));
193
196
  }
194
197
  });
195
198
  }
@@ -288,6 +291,7 @@ export class Table extends ViewComponent {
288
291
  if (cells[columnIndex] !== cells[newColumnIndex] ||
289
292
  columnIndex === newColumnIndex) {
290
293
  const cell = this.j.createInside.element('td');
294
+ Dom.append(cell, this.j.createInside.element('br'));
291
295
  if (insertAfter) {
292
296
  Dom.after(cells[columnIndex], cell);
293
297
  }
@@ -516,7 +520,8 @@ export class Table extends ViewComponent {
516
520
  Table.__setColumnWidthByDelta(table, first_j, 0, true, __marked);
517
521
  }
518
522
  }
519
- first.innerHTML = html.join('<br/>');
523
+ first.innerHTML =
524
+ html.join('<br/>') || '<br/>';
520
525
  instance(jodit).addSelection(first);
521
526
  alreadyMerged.delete(first);
522
527
  Table.__unmark(__marked);
@@ -99,8 +99,8 @@ export const FileSelectorWidget = (editor, callbacks, elm, close, isImage = true
99
99
  const box = TabsWidget(editor, tabs);
100
100
  // Marks this popup so its tabs auto-size to their labels (see
101
101
  // `.jodit-file-selector` in tabs.less) — long localized tab captions are no
102
- // longer clipped — while leaving other tab popups (link, video …)
103
- // untouched.
102
+ // longer clipped — while leaving other tab popups (link …) untouched. The
103
+ // video popup, which has an Upload tab too, uses it as well.
104
104
  box.classList.add('jodit-file-selector');
105
105
  return box;
106
106
  };
@@ -56,6 +56,10 @@ export class backspace extends Plugin {
56
56
  if (!sel.isFocused()) {
57
57
  sel.focus();
58
58
  }
59
+ // Focused with no range, as WebKit leaves it after a click on an embed
60
+ if (!sel.isInsideArea) {
61
+ return;
62
+ }
59
63
  if (checkNotCollapsed(jodit)) {
60
64
  jodit.e.fire('backSpaceAfterDelete', backspace);
61
65
  return false;
@@ -40,6 +40,13 @@ export declare class cleanHtml extends Plugin {
40
40
  protected onBeforeSetNativeEditorValue(data: {
41
41
  value: string;
42
42
  }): boolean;
43
+ /**
44
+ * Replace old tags (`<b>` with `<strong>`, `<i>` with `<em>`) in an
45
+ * assigned value straight away. The walker would get to them only after
46
+ * every node before them, seconds later on a long document, and until
47
+ * then Bold does not treat `<b>` as bold, and the swap can move the caret.
48
+ */
49
+ private __replaceOldTags;
43
50
  /**
44
51
  * Collapse a value that holds only a single empty block (e.g.
45
52
  * `<p><br></p>` left after deleting all content) to an empty string —
@@ -20,6 +20,7 @@ import { pluginSystem } from "../../core/global.js";
20
20
  import { safeHTML } from "../../core/helpers/html/safe-html.js";
21
21
  import { Plugin } from "../../core/plugin/plugin.js";
22
22
  import "./config.js";
23
+ import { replaceOldTags } from "./helpers/visitor/filters/replace-old-tags.js";
23
24
  import { getHash, removeFormatForCollapsedSelection, removeFormatForSelection, visitNodeWalker } from "./helpers/index.js";
24
25
  /**
25
26
  * Clean HTML after removeFormat and insertHorizontalRule command
@@ -119,11 +120,27 @@ export class cleanHtml extends Plugin {
119
120
  ];
120
121
  sandBox.innerHTML = data.value;
121
122
  this.j.e.fire('safeHTML', sandBox);
123
+ this.__replaceOldTags(sandBox);
122
124
  data.value = sandBox.innerHTML;
123
125
  safeHTML(sandBox, { safeJavaScriptLink: true, removeOnError: true });
124
126
  Dom.safeRemove(iframe);
125
127
  return false;
126
128
  }
129
+ /**
130
+ * Replace old tags (`<b>` with `<strong>`, `<i>` with `<em>`) in an
131
+ * assigned value straight away. The walker would get to them only after
132
+ * every node before them, seconds later on a long document, and until
133
+ * then Bold does not treat `<b>` as bold, and the swap can move the caret.
134
+ */
135
+ __replaceOldTags(box) {
136
+ const { replaceOldTags: list, disableCleanFilter } = this.j.o.cleanHTML;
137
+ if (!list || (disableCleanFilter === null || disableCleanFilter === void 0 ? void 0 : disableCleanFilter.has('replaceOldTags'))) {
138
+ return;
139
+ }
140
+ Dom.each(box, node => {
141
+ replaceOldTags(this.j, node, false);
142
+ });
143
+ }
127
144
  /**
128
145
  * Collapse a value that holds only a single empty block (e.g.
129
146
  * `<p><br></p>` left after deleting all content) to an empty string —
@@ -24,7 +24,32 @@ function replaceIfMatched(jodit, oldParent, list) {
24
24
  }
25
25
  const tagName = list[oldParent.nodeName.toLowerCase()] || list[oldParent.nodeName];
26
26
  if (tagName) {
27
- return Dom.replace(oldParent, tagName, jodit.createInside, true, false);
27
+ const restoreSelection = keepSelection(jodit, oldParent);
28
+ const newParent = Dom.replace(oldParent, tagName, jodit.createInside, true, false);
29
+ restoreSelection === null || restoreSelection === void 0 ? void 0 : restoreSelection(newParent);
30
+ return newParent;
28
31
  }
29
32
  return oldParent;
30
33
  }
34
+ /**
35
+ * Moving the children out of an element collapses a selection inside them to
36
+ * where the element was, so the caret jumps to the start of the text being
37
+ * typed. Returns a callback that puts the selection back on the same nodes.
38
+ *
39
+ * Like `removeEmptyTextNode`, it leaves the selection alone once focus has
40
+ * left the editor, because selecting would take focus back.
41
+ */
42
+ function keepSelection(jodit, elm) {
43
+ const { sel } = jodit.s;
44
+ if (!(sel === null || sel === void 0 ? void 0 : sel.anchorNode) || !sel.focusNode || !jodit.s.isFocused()) {
45
+ return null;
46
+ }
47
+ const { anchorNode, anchorOffset, focusNode, focusOffset } = sel;
48
+ if (!Dom.isOrContains(elm, anchorNode) &&
49
+ !Dom.isOrContains(elm, focusNode)) {
50
+ return null;
51
+ }
52
+ return (newElm) => {
53
+ sel.setBaseAndExtent(anchorNode === elm ? newElm : anchorNode, anchorOffset, focusNode === elm ? newElm : focusNode, focusOffset);
54
+ };
55
+ }
@@ -10,7 +10,8 @@
10
10
  import type { IJodit, Nullable } from "../../../types/index";
11
11
  /**
12
12
  * Checks whether the insertion of an element at the current location is allowed,
13
- * if it is not allowed, it deletes an empty block element or moves the cursor after it
13
+ * if it is not allowed, it deletes an empty block element, moves the cursor
14
+ * before or after it, or splits it at the cursor
14
15
  * @internal
15
16
  */
16
17
  export declare function checkBlockNesting(jodit: IJodit, node: Nullable<Node>): void;
@@ -4,9 +4,33 @@
4
4
  * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
5
  */
6
6
  import { Dom } from "../../../core/dom/dom.js";
7
+ /**
8
+ * Moves the cursor out of a block that can't hold the inserted block: before
9
+ * or after it when the cursor is at its edge, or between its two halves when
10
+ * the cursor is inside its content. An empty block is removed
11
+ */
12
+ function moveCursorOutOf(jodit, parent) {
13
+ var _a;
14
+ const { s } = jodit;
15
+ if (Dom.isEmpty(parent)) {
16
+ s.setCursorAfter(parent);
17
+ Dom.safeRemove(parent);
18
+ return;
19
+ }
20
+ if (s.cursorOnTheRight(parent)) {
21
+ s.setCursorAfter(parent);
22
+ return;
23
+ }
24
+ if (s.cursorOnTheLeft(parent)) {
25
+ s.setCursorBefore(parent);
26
+ return;
27
+ }
28
+ s.setCursorAfter((_a = s.splitSelection(parent)) !== null && _a !== void 0 ? _a : parent);
29
+ }
7
30
  /**
8
31
  * Checks whether the insertion of an element at the current location is allowed,
9
- * if it is not allowed, it deletes an empty block element or moves the cursor after it
32
+ * if it is not allowed, it deletes an empty block element, moves the cursor
33
+ * before or after it, or splits it at the cursor
10
34
  * @internal
11
35
  */
12
36
  export function checkBlockNesting(jodit, node) {
@@ -42,10 +66,7 @@ export function checkBlockNesting(jodit, node) {
42
66
  parent = current;
43
67
  }
44
68
  if (parent) {
45
- jodit.s.setCursorAfter(parent);
46
- if (Dom.isEmpty(parent)) {
47
- Dom.safeRemove(parent);
48
- }
69
+ moveCursorOutOf(jodit, parent);
49
70
  }
50
71
  }
51
72
  }
@@ -18,5 +18,13 @@ export function moveCursorOutFromSpecialTags(jodit, fake, tags) {
18
18
  else if (s.cursorOnTheLeft(link, fake)) {
19
19
  Dom.before(link, fake);
20
20
  }
21
+ else {
22
+ return;
23
+ }
24
+ // The block is split at the caret, so the caret leaves the link too:
25
+ // beside the fake, since Safari mislays a caret inside a removed node
26
+ const range = s.createRange();
27
+ range.setStartBefore(fake);
28
+ s.selectRange(range);
21
29
  }
22
30
  }
@@ -3,6 +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
+ import { INSEPARABLE_TAGS } from "../../core/constants.js";
6
7
  import { Dom } from "../../core/dom/index.js";
7
8
  import { pluginSystem } from "../../core/global.js";
8
9
  import { Config } from "../../config.js";
@@ -77,9 +78,11 @@ export function focus(editor) {
77
78
  editor.e
78
79
  .off(editor.editor, 'mousedown.autofocus')
79
80
  .on(editor.editor, 'mousedown.autofocus', (e) => {
81
+ // An empty block takes the caret, but a video, say, can't hold one
80
82
  if (editor.isEditorMode() &&
81
83
  e.target &&
82
84
  Dom.isBlock(e.target) &&
85
+ !Dom.isTag(e.target, INSEPARABLE_TAGS) &&
83
86
  !e.target.childNodes.length) {
84
87
  if (editor.editor === e.target) {
85
88
  editor.s.focus();