jodit 4.18.2 → 4.18.4

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 (226) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/es2015/jodit.css +66 -6
  3. package/es2015/jodit.fat.min.css +1 -1
  4. package/es2015/jodit.fat.min.js +36 -11
  5. package/es2015/jodit.js +1435 -327
  6. package/es2015/jodit.min.css +1 -1
  7. package/es2015/jodit.min.js +41 -16
  8. package/es2015/plugins/debug/debug.css +1 -1
  9. package/es2015/plugins/debug/debug.js +1 -1
  10. package/es2015/plugins/debug/debug.min.js +1 -1
  11. package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
  12. package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
  13. package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
  14. package/es2018/jodit.fat.min.css +1 -1
  15. package/es2018/jodit.fat.min.js +36 -11
  16. package/es2018/jodit.min.css +1 -1
  17. package/es2018/jodit.min.js +36 -11
  18. package/es2018/plugins/debug/debug.min.js +1 -1
  19. package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
  20. package/es2021/jodit.css +66 -6
  21. package/es2021/jodit.fat.min.css +1 -1
  22. package/es2021/jodit.fat.min.js +45 -20
  23. package/es2021/jodit.js +1365 -271
  24. package/es2021/jodit.min.css +1 -1
  25. package/es2021/jodit.min.js +45 -20
  26. package/es2021/plugins/debug/debug.css +1 -1
  27. package/es2021/plugins/debug/debug.js +1 -1
  28. package/es2021/plugins/debug/debug.min.js +1 -1
  29. package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
  30. package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
  31. package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
  32. package/es2021.en/jodit.css +66 -6
  33. package/es2021.en/jodit.fat.min.css +1 -1
  34. package/es2021.en/jodit.fat.min.js +51 -26
  35. package/es2021.en/jodit.js +1339 -245
  36. package/es2021.en/jodit.min.css +1 -1
  37. package/es2021.en/jodit.min.js +51 -26
  38. package/es2021.en/plugins/debug/debug.css +1 -1
  39. package/es2021.en/plugins/debug/debug.js +1 -1
  40. package/es2021.en/plugins/debug/debug.min.js +1 -1
  41. package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
  42. package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
  43. package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
  44. package/es5/jodit.css +82 -7
  45. package/es5/jodit.fat.min.css +1 -1
  46. package/es5/jodit.fat.min.js +2 -2
  47. package/es5/jodit.js +1629 -354
  48. package/es5/jodit.min.css +3 -3
  49. package/es5/jodit.min.js +2 -2
  50. package/es5/plugins/debug/debug.css +1 -1
  51. package/es5/plugins/debug/debug.js +1 -1
  52. package/es5/plugins/debug/debug.min.js +1 -1
  53. package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
  54. package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
  55. package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
  56. package/es5/polyfills.fat.min.js +1 -1
  57. package/es5/polyfills.js +1 -1
  58. package/es5/polyfills.min.js +1 -1
  59. package/esm/config.d.ts +29 -28
  60. package/esm/config.js +19 -21
  61. package/esm/core/constants.js +1 -1
  62. package/esm/core/helpers/utils/align.d.ts +1 -1
  63. package/esm/core/helpers/utils/align.js +2 -3
  64. package/esm/core/helpers/utils/index.d.ts +1 -0
  65. package/esm/core/helpers/utils/index.js +1 -0
  66. package/esm/core/helpers/utils/prepare-media.d.ts +19 -0
  67. package/esm/core/helpers/utils/prepare-media.js +31 -0
  68. package/esm/core/selection/style/api/wrap-unwrapped-text.js +3 -2
  69. package/esm/jodit.js +14 -4
  70. package/esm/langs/ar.d.ts +7 -0
  71. package/esm/langs/ar.js +8 -1
  72. package/esm/langs/az.d.ts +7 -0
  73. package/esm/langs/az.js +8 -1
  74. package/esm/langs/cs_cz.d.ts +7 -0
  75. package/esm/langs/cs_cz.js +9 -1
  76. package/esm/langs/de.d.ts +7 -0
  77. package/esm/langs/de.js +8 -1
  78. package/esm/langs/es.d.ts +7 -0
  79. package/esm/langs/es.js +8 -1
  80. package/esm/langs/fa.d.ts +7 -0
  81. package/esm/langs/fa.js +8 -1
  82. package/esm/langs/fi.d.ts +7 -0
  83. package/esm/langs/fi.js +8 -1
  84. package/esm/langs/fr.d.ts +7 -0
  85. package/esm/langs/fr.js +8 -1
  86. package/esm/langs/he.d.ts +7 -0
  87. package/esm/langs/he.js +8 -1
  88. package/esm/langs/hu.d.ts +7 -0
  89. package/esm/langs/hu.js +8 -1
  90. package/esm/langs/id.d.ts +7 -0
  91. package/esm/langs/id.js +8 -1
  92. package/esm/langs/it.d.ts +7 -0
  93. package/esm/langs/it.js +8 -1
  94. package/esm/langs/ja.d.ts +7 -0
  95. package/esm/langs/ja.js +8 -1
  96. package/esm/langs/ko.d.ts +7 -0
  97. package/esm/langs/ko.js +8 -1
  98. package/esm/langs/mn.d.ts +7 -0
  99. package/esm/langs/mn.js +8 -1
  100. package/esm/langs/nl.d.ts +7 -0
  101. package/esm/langs/nl.js +8 -1
  102. package/esm/langs/no.d.ts +7 -0
  103. package/esm/langs/no.js +8 -1
  104. package/esm/langs/pl.d.ts +7 -0
  105. package/esm/langs/pl.js +8 -1
  106. package/esm/langs/pt_br.d.ts +7 -0
  107. package/esm/langs/pt_br.js +8 -1
  108. package/esm/langs/ru.d.ts +7 -0
  109. package/esm/langs/ru.js +8 -1
  110. package/esm/langs/sk.d.ts +7 -0
  111. package/esm/langs/sk.js +8 -1
  112. package/esm/langs/sv.d.ts +7 -0
  113. package/esm/langs/sv.js +8 -1
  114. package/esm/langs/tr.d.ts +7 -0
  115. package/esm/langs/tr.js +8 -1
  116. package/esm/langs/ua.d.ts +7 -0
  117. package/esm/langs/ua.js +8 -1
  118. package/esm/langs/zh_cn.d.ts +7 -0
  119. package/esm/langs/zh_cn.js +8 -1
  120. package/esm/langs/zh_tw.d.ts +7 -0
  121. package/esm/langs/zh_tw.js +8 -1
  122. package/esm/modules/history/history.d.ts +17 -2
  123. package/esm/modules/history/history.js +41 -19
  124. package/esm/modules/table/table.js +7 -2
  125. package/esm/modules/widget/file-selector/file-selector.js +2 -2
  126. package/esm/plugins/all.d.ts +1 -0
  127. package/esm/plugins/all.js +1 -0
  128. package/esm/plugins/backspace/backspace.js +4 -0
  129. package/esm/plugins/clean-html/clean-html.d.ts +7 -0
  130. package/esm/plugins/clean-html/clean-html.js +17 -0
  131. package/esm/plugins/clean-html/helpers/visitor/filters/replace-old-tags.js +26 -1
  132. package/esm/plugins/dtd/before-insert/check-block-nesting.d.ts +2 -1
  133. package/esm/plugins/dtd/before-insert/check-block-nesting.js +26 -5
  134. package/esm/plugins/enter/helpers/move-cursor-out-from-specal-tags.js +8 -0
  135. package/esm/plugins/focus/focus.js +3 -0
  136. package/esm/plugins/hr/hr.js +21 -5
  137. package/esm/plugins/iframe/config.js +8 -3
  138. package/esm/plugins/image-processor/image-processor.d.ts +4 -1
  139. package/esm/plugins/image-processor/image-processor.js +9 -3
  140. package/esm/plugins/image-properties/readers/align.d.ts +2 -2
  141. package/esm/plugins/index.d.ts +1 -0
  142. package/esm/plugins/inline-popup/config/items/iframe.d.ts +9 -1
  143. package/esm/plugins/inline-popup/config/items/iframe.js +15 -0
  144. package/esm/plugins/inline-popup/inline-popup.d.ts +5 -0
  145. package/esm/plugins/inline-popup/inline-popup.js +26 -2
  146. package/esm/plugins/link/link.js +5 -2
  147. package/esm/plugins/media/config.d.ts +2 -1
  148. package/esm/plugins/media/media.js +37 -9
  149. package/esm/plugins/paste/helpers.d.ts +8 -0
  150. package/esm/plugins/paste/helpers.js +18 -1
  151. package/esm/plugins/paste/paste.js +2 -2
  152. package/esm/plugins/resizer/config.d.ts +5 -3
  153. package/esm/plugins/resizer/config.js +8 -2
  154. package/esm/plugins/resizer/resizer.d.ts +10 -0
  155. package/esm/plugins/resizer/resizer.js +72 -18
  156. package/esm/plugins/select-cells/select-cells.d.ts +10 -0
  157. package/esm/plugins/select-cells/select-cells.js +54 -6
  158. package/esm/plugins/table/config.js +51 -1
  159. package/esm/plugins/table-keyboard-navigation/table-keyboard-navigation.js +1 -1
  160. package/esm/plugins/video/config.d.ts +6 -0
  161. package/esm/plugins/video/config.js +78 -82
  162. package/esm/plugins/video-properties/interface.d.ts +31 -0
  163. package/esm/plugins/video-properties/interface.js +6 -0
  164. package/esm/plugins/video-properties/readers.d.ts +26 -0
  165. package/esm/plugins/video-properties/readers.js +65 -0
  166. package/esm/plugins/video-properties/replace.d.ts +15 -0
  167. package/esm/plugins/video-properties/replace.js +97 -0
  168. package/esm/plugins/video-properties/ui/ui-video-form.d.ts +41 -0
  169. package/esm/plugins/video-properties/ui/ui-video-form.js +228 -0
  170. package/esm/plugins/video-properties/video-properties.d.ts +37 -0
  171. package/esm/plugins/video-properties/video-properties.js +175 -0
  172. package/esm/plugins/video-properties/writers.d.ts +15 -0
  173. package/esm/plugins/video-properties/writers.js +73 -0
  174. package/esm/types/types.d.ts +1 -1
  175. package/package.json +1 -1
  176. package/types/config.d.ts +29 -28
  177. package/types/core/helpers/utils/align.d.ts +1 -1
  178. package/types/core/helpers/utils/index.d.ts +1 -0
  179. package/types/core/helpers/utils/prepare-media.d.ts +19 -0
  180. package/types/langs/ar.d.ts +7 -0
  181. package/types/langs/az.d.ts +7 -0
  182. package/types/langs/cs_cz.d.ts +7 -0
  183. package/types/langs/de.d.ts +7 -0
  184. package/types/langs/es.d.ts +7 -0
  185. package/types/langs/fa.d.ts +7 -0
  186. package/types/langs/fi.d.ts +7 -0
  187. package/types/langs/fr.d.ts +7 -0
  188. package/types/langs/he.d.ts +7 -0
  189. package/types/langs/hu.d.ts +7 -0
  190. package/types/langs/id.d.ts +7 -0
  191. package/types/langs/it.d.ts +7 -0
  192. package/types/langs/ja.d.ts +7 -0
  193. package/types/langs/ko.d.ts +7 -0
  194. package/types/langs/mn.d.ts +7 -0
  195. package/types/langs/nl.d.ts +7 -0
  196. package/types/langs/no.d.ts +7 -0
  197. package/types/langs/pl.d.ts +7 -0
  198. package/types/langs/pt_br.d.ts +7 -0
  199. package/types/langs/ru.d.ts +7 -0
  200. package/types/langs/sk.d.ts +7 -0
  201. package/types/langs/sv.d.ts +7 -0
  202. package/types/langs/tr.d.ts +7 -0
  203. package/types/langs/ua.d.ts +7 -0
  204. package/types/langs/zh_cn.d.ts +7 -0
  205. package/types/langs/zh_tw.d.ts +7 -0
  206. package/types/modules/history/history.d.ts +17 -2
  207. package/types/plugins/clean-html/clean-html.d.ts +7 -0
  208. package/types/plugins/dtd/before-insert/check-block-nesting.d.ts +2 -1
  209. package/types/plugins/image-processor/image-processor.d.ts +4 -1
  210. package/types/plugins/image-properties/readers/align.d.ts +2 -2
  211. package/types/plugins/index.d.ts +1 -0
  212. package/types/plugins/inline-popup/config/items/iframe.d.ts +9 -1
  213. package/types/plugins/inline-popup/inline-popup.d.ts +5 -0
  214. package/types/plugins/media/config.d.ts +2 -1
  215. package/types/plugins/paste/helpers.d.ts +8 -0
  216. package/types/plugins/resizer/config.d.ts +5 -3
  217. package/types/plugins/resizer/resizer.d.ts +10 -0
  218. package/types/plugins/select-cells/select-cells.d.ts +10 -0
  219. package/types/plugins/video/config.d.ts +6 -0
  220. package/types/plugins/video-properties/interface.d.ts +31 -0
  221. package/types/plugins/video-properties/readers.d.ts +26 -0
  222. package/types/plugins/video-properties/replace.d.ts +15 -0
  223. package/types/plugins/video-properties/ui/ui-video-form.d.ts +41 -0
  224. package/types/plugins/video-properties/video-properties.d.ts +37 -0
  225. package/types/plugins/video-properties/writers.d.ts +15 -0
  226. package/types/types/types.d.ts +1 -1
@@ -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
  };
@@ -68,6 +68,7 @@ import "./table/table";
68
68
  import "./table-keyboard-navigation/table-keyboard-navigation";
69
69
  import "./toolbar-navigation/toolbar-navigation";
70
70
  import "./video/video";
71
+ import "./video-properties/video-properties";
71
72
  import "./wrap-nodes/wrap-nodes";
72
73
  import "./dtd/dtd";
73
74
  import "./xpath/xpath";
@@ -69,6 +69,7 @@ import "./table/table.js";
69
69
  import "./table-keyboard-navigation/table-keyboard-navigation.js";
70
70
  import "./toolbar-navigation/toolbar-navigation.js";
71
71
  import "./video/video.js";
72
+ import "./video-properties/video-properties.js";
72
73
  import "./wrap-nodes/wrap-nodes.js";
73
74
  import "./dtd/dtd.js";
74
75
  import "./xpath/xpath.js";
@@ -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();
@@ -20,18 +20,34 @@ export function hr(editor) {
20
20
  group: 'insert'
21
21
  });
22
22
  editor.registerCommand('insertHorizontalRule', () => {
23
+ var _a;
23
24
  const elm = editor.createInside.element('hr');
24
25
  editor.s.insertNode(elm, false, false);
25
26
  const block = Dom.closest(elm.parentElement, Dom.isBlock, editor.editor);
26
- if (block && Dom.isEmpty(block) && block !== editor.editor) {
27
+ // A block that holds other blocks, such as a list item or a table
28
+ // cell, keeps the line
29
+ if (block &&
30
+ Dom.isEmpty(block) &&
31
+ block !== editor.editor &&
32
+ !((_a = editor.o.dtd) === null || _a === void 0 ? void 0 : _a.blockLimits[block.nodeName.toLowerCase()])) {
27
33
  Dom.after(block, elm);
28
34
  Dom.safeRemove(block);
29
35
  }
30
- let p = Dom.next(elm, Dom.isBlock, editor.editor, false);
31
- if (!p) {
32
- p = editor.createInside.element(editor.o.enter);
33
- Dom.after(elm, p);
36
+ const next = Dom.findNotEmptySibling(elm, false);
37
+ if (Dom.isBlock(next)) {
38
+ editor.s.setCursorIn(next, true);
39
+ return false;
34
40
  }
41
+ // In a list item or a table cell the text goes on after the line, as
42
+ // it does with the browsers' own command
43
+ if (next ||
44
+ (elm.parentNode !== editor.editor &&
45
+ !Dom.isBlock(Dom.findNotEmptySibling(elm, true)))) {
46
+ editor.s.setCursorAfter(elm);
47
+ return false;
48
+ }
49
+ const p = editor.createInside.element(editor.o.enter);
50
+ Dom.after(elm, p);
35
51
  editor.s.setCursorIn(p);
36
52
  return false;
37
53
  });
@@ -51,13 +51,15 @@ Config.prototype.iframeStyle =
51
51
  'p{' +
52
52
  'margin-top:0;' +
53
53
  '}' +
54
- '.jodit_editor .jodit_iframe_wrapper{' +
54
+ '[data-jodit_iframe_wrapper]{' +
55
55
  'display: block;' +
56
- 'clear: both;' +
57
56
  'user-select: none;' +
58
57
  'position: relative;' +
59
58
  '}' +
60
- '.jodit_editor .jodit_iframe_wrapper:after {' +
59
+ '[data-jodit_iframe_wrapper] iframe{' +
60
+ 'position: relative;' +
61
+ '}' +
62
+ '[data-jodit_iframe_wrapper]:after {' +
61
63
  'position:absolute;' +
62
64
  'content:"";' +
63
65
  'z-index:1;' +
@@ -69,6 +71,9 @@ Config.prototype.iframeStyle =
69
71
  'display: block;' +
70
72
  'background: rgba(0, 0, 0, 0);' +
71
73
  '} ' +
74
+ '[data-jodit_iframe_wrapper][data-jodit-wrapper_active=true] iframe{' +
75
+ 'z-index: 2;' +
76
+ '}' +
72
77
  '.jodit-wysiwyg_image-selected::selection,' +
73
78
  '.jodit-wysiwyg_image-selected *:not(img)::selection{' +
74
79
  'background:transparent' +
@@ -25,7 +25,10 @@ export declare class imageProcessor extends Plugin {
25
25
  }): void;
26
26
  /**
27
27
  * While the selection is an image, only the image is highlighted: Safari
28
- * paints the rest of a block image's line with its parent's highlight. See #1528
28
+ * paints the rest of a block image's line with its parent's highlight. See #1528.
29
+ * Media in a wrapper (`jodit-media`, the resizer's `jodit` for an embed, or
30
+ * one from `mediaWrappers`) isn't highlighted either, as Safari paints its
31
+ * wrapper's whole line, and its resize frame shows it's selected
29
32
  */
30
33
  private onSelectionChange;
31
34
  protected afterChange(data: {
@@ -17,13 +17,14 @@ import { INVISIBLE_SPACE_REG_EXP, SOURCE_CONSUMER } from "../../core/constants.j
17
17
  import { autobind, cached, debounce, watch } from "../../core/decorators/index.js";
18
18
  import { Dom } from "../../core/dom/dom.js";
19
19
  import { pluginSystem } from "../../core/global.js";
20
- import { $$, dataBind } from "../../core/helpers/index.js";
20
+ import { $$, dataBind, getWrappedMedia } from "../../core/helpers/index.js";
21
21
  import { Plugin } from "../../core/plugin/index.js";
22
22
  import { dataURItoBlob } from "../../modules/uploader/helpers/data-uri-to-blob.js";
23
23
  import "./config.js";
24
24
  const JODIT_IMAGE_PROCESSOR_BINDED = '__jodit_imageprocessor_binded';
25
25
  const JODIT_IMAGE_BLOB_ID = JODIT_IMAGE_PROCESSOR_BINDED + 'blob-id';
26
26
  const IMAGE_SELECTED = 'jodit-wysiwyg_image-selected';
27
+ const SELECTED_TAGS = new Set(['img', 'jodit', 'jodit-media']);
27
28
  /**
28
29
  * Change editor's size after load all images
29
30
  */
@@ -66,7 +67,10 @@ export class imageProcessor extends Plugin {
66
67
  }
67
68
  /**
68
69
  * While the selection is an image, only the image is highlighted: Safari
69
- * paints the rest of a block image's line with its parent's highlight. See #1528
70
+ * paints the rest of a block image's line with its parent's highlight. See #1528.
71
+ * Media in a wrapper (`jodit-media`, the resizer's `jodit` for an embed, or
72
+ * one from `mediaWrappers`) isn't highlighted either, as Safari paints its
73
+ * wrapper's whole line, and its resize frame shows it's selected
70
74
  */
71
75
  onSelectionChange() {
72
76
  const { jodit } = this;
@@ -117,9 +121,11 @@ function isImageSelected(jodit) {
117
121
  return false;
118
122
  }
119
123
  const { startContainer, startOffset, endContainer, endOffset } = sel.getRangeAt(0);
124
+ const node = startContainer.childNodes[startOffset];
120
125
  return (startContainer === endContainer &&
121
126
  endOffset === startOffset + 1 &&
122
- Dom.isTag(startContainer.childNodes[startOffset], 'img') &&
127
+ (Dom.isTag(node, SELECTED_TAGS) ||
128
+ Boolean(getWrappedMedia(jodit, node))) &&
123
129
  Dom.isOrContains(jodit.editor, startContainer));
124
130
  }
125
131
  /**
@@ -6,9 +6,9 @@
6
6
  /**
7
7
  * @module plugins/image-properties
8
8
  */
9
- import type { ImageAlignClasses, Nullable } from "../../../types/index";
9
+ import type { HAlignClasses, Nullable } from "../../../types/index";
10
10
  import type { EditValues } from "../interface";
11
11
  /**
12
12
  * @private
13
13
  */
14
- export declare function readAlign(image: HTMLImageElement, values: EditValues, classes?: Nullable<ImageAlignClasses>): void;
14
+ export declare function readAlign(image: HTMLImageElement, values: EditValues, classes?: Nullable<HAlignClasses>): void;
@@ -68,6 +68,7 @@ import "./table/table";
68
68
  import "./table-keyboard-navigation/table-keyboard-navigation";
69
69
  import "./toolbar-navigation/toolbar-navigation";
70
70
  import "./video/video";
71
+ import "./video-properties/video-properties";
71
72
  import "./wrap-nodes/wrap-nodes";
72
73
  import "./dtd/dtd";
73
74
  import "./xpath/xpath";
@@ -6,10 +6,18 @@
6
6
  /**
7
7
  * @module plugins/inline-popup
8
8
  */
9
- import type { IJodit } from "../../../../types/index";
9
+ import type { IJodit, Nullable } from "../../../../types/index";
10
10
  declare const _default: (import("../../../../types/index").IControlType<IJodit, import("../../../../types/index").IToolbarButton> | {
11
+ name: string;
12
+ icon: string;
13
+ tooltip: string;
14
+ popup: (editor: IJodit, target: Nullable<Node>, close: () => void) => HTMLElement | void;
15
+ exec?: undefined;
16
+ } | {
11
17
  name: string;
12
18
  tooltip: string;
13
19
  exec: (editor: IJodit, image: HTMLImageElement) => void;
20
+ icon?: undefined;
21
+ popup?: undefined;
14
22
  })[];
15
23
  export default _default;
@@ -5,6 +5,14 @@
5
5
  */
6
6
  import { align } from "./img.js";
7
7
  export default [
8
+ {
9
+ name: 'replace',
10
+ icon: 'video',
11
+ tooltip: 'Replace',
12
+ popup: (editor, target, close) => target
13
+ ? editor.e.fire('generateReplaceVideoTabs', target, close)
14
+ : undefined
15
+ },
8
16
  {
9
17
  name: 'bin',
10
18
  tooltip: 'Delete',
@@ -12,5 +20,12 @@ export default [
12
20
  image && editor.s.removeNode(image);
13
21
  }
14
22
  },
23
+ {
24
+ name: 'pencil',
25
+ tooltip: 'Edit',
26
+ exec: (editor, target) => {
27
+ target && editor.e.fire('openVideoProperties', target);
28
+ }
29
+ },
15
30
  align
16
31
  ];
@@ -20,6 +20,11 @@ export declare class inlinePopup extends Plugin {
20
20
  private get popup();
21
21
  private get toolbar();
22
22
  private onClick;
23
+ /**
24
+ * The box the popup opens beside: the target's, with room for the
25
+ * resizer's handles when the target gets them, so it doesn't cover them
26
+ */
27
+ private __bound;
23
28
  /**
24
29
  * Show inline popup with some toolbar
25
30
  *
@@ -16,7 +16,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
16
16
  import { autobind, cache, debounce, wait, watch } from "../../core/decorators/index.js";
17
17
  import { Dom } from "../../core/dom/index.js";
18
18
  import { pluginSystem } from "../../core/global.js";
19
- import { camelCase, getMediaWrapper, isArray, isFunction, isString, keys, position, splitArray, toArray } from "../../core/helpers/index.js";
19
+ import { camelCase, getMediaWrapper, getWrappedMedia, isArray, isFunction, isString, keys, position, splitArray, toArray } from "../../core/helpers/index.js";
20
20
  import { Plugin } from "../../core/plugin/index.js";
21
21
  import { UIElement } from "../../core/ui/index.js";
22
22
  import { Popup } from "../../core/ui/popup/index.js";
@@ -53,10 +53,34 @@ export class inlinePopup extends Plugin {
53
53
  ? 'jodit-media'
54
54
  : target === null || target === void 0 ? void 0 : target.nodeName.toLowerCase();
55
55
  if (target && type && this.canShowPopupForType(type)) {
56
- this.showPopup(() => position(target, this.j), type, target);
56
+ const isMedia = type === 'jodit-media';
57
+ // Beside the media in a wrapper kept in the content, which can fill
58
+ // the line
59
+ const bound = (mediaWrapper && getWrappedMedia(this.j, mediaWrapper)) ||
60
+ target;
61
+ this.showPopup(() => this.__bound(bound, isMedia), type, target);
57
62
  return false;
58
63
  }
59
64
  }
65
+ /**
66
+ * The box the popup opens beside: the target's, with room for the
67
+ * resizer's handles when the target gets them, so it doesn't cover them
68
+ */
69
+ __bound(target, isMedia) {
70
+ const bound = position(target, this.j);
71
+ const resizable = this.j.o.allowResizeTags;
72
+ if (!isMedia && !(resizable && Dom.isTag(target, resizable))) {
73
+ return bound;
74
+ }
75
+ const room = parseInt(this.j.ow
76
+ .getComputedStyle(this.j.container)
77
+ .getPropertyValue('--jd-resizer-handle-size'), 10) / 2 || 0;
78
+ return {
79
+ ...bound,
80
+ top: bound.top - room,
81
+ height: bound.height + room * 2
82
+ };
83
+ }
60
84
  /**
61
85
  * Show inline popup with some toolbar
62
86
  *