jodit 4.18.3 → 4.18.6

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 (237) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/es2015/jodit.css +72 -34
  3. package/es2015/jodit.fat.min.css +1 -1
  4. package/es2015/jodit.fat.min.js +35 -10
  5. package/es2015/jodit.js +1967 -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 +35 -10
  16. package/es2018/jodit.min.css +1 -1
  17. package/es2018/jodit.min.js +35 -10
  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 +72 -34
  21. package/es2021/jodit.fat.min.css +1 -1
  22. package/es2021/jodit.fat.min.js +42 -17
  23. package/es2021/jodit.js +1949 -323
  24. package/es2021/jodit.min.css +1 -1
  25. package/es2021/jodit.min.js +42 -17
  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 +72 -34
  33. package/es2021.en/jodit.fat.min.css +1 -1
  34. package/es2021.en/jodit.fat.min.js +42 -17
  35. package/es2021.en/jodit.js +1923 -297
  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 +85 -38
  45. package/es5/jodit.fat.min.css +1 -1
  46. package/es5/jodit.fat.min.js +2 -2
  47. package/es5/jodit.js +2225 -379
  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 +28 -26
  60. package/esm/config.js +19 -21
  61. package/esm/core/constants.js +1 -1
  62. package/esm/core/helpers/html/apply-styles.js +3 -0
  63. package/esm/core/helpers/html/clean-from-word.js +3 -0
  64. package/esm/core/helpers/html/strip-tags.js +24 -6
  65. package/esm/core/helpers/html/word-lists.d.ts +29 -0
  66. package/esm/core/helpers/html/word-lists.js +158 -0
  67. package/esm/core/helpers/utils/align.d.ts +1 -1
  68. package/esm/core/helpers/utils/align.js +2 -3
  69. package/esm/core/selection/helpers/index.d.ts +1 -0
  70. package/esm/core/selection/helpers/index.js +1 -0
  71. package/esm/core/selection/helpers/is-moved.d.ts +15 -0
  72. package/esm/core/selection/helpers/is-moved.js +22 -0
  73. package/esm/core/selection/style/api/list/toggle-ordered-list.js +40 -3
  74. package/esm/core/selection/style/transactions.js +5 -2
  75. package/esm/jodit.js +14 -4
  76. package/esm/langs/ar.d.ts +10 -0
  77. package/esm/langs/ar.js +11 -1
  78. package/esm/langs/az.d.ts +10 -0
  79. package/esm/langs/az.js +11 -1
  80. package/esm/langs/cs_cz.d.ts +10 -0
  81. package/esm/langs/cs_cz.js +12 -1
  82. package/esm/langs/de.d.ts +10 -0
  83. package/esm/langs/de.js +11 -1
  84. package/esm/langs/es.d.ts +10 -0
  85. package/esm/langs/es.js +11 -1
  86. package/esm/langs/fa.d.ts +10 -0
  87. package/esm/langs/fa.js +11 -1
  88. package/esm/langs/fi.d.ts +10 -0
  89. package/esm/langs/fi.js +11 -1
  90. package/esm/langs/fr.d.ts +10 -0
  91. package/esm/langs/fr.js +11 -1
  92. package/esm/langs/he.d.ts +10 -0
  93. package/esm/langs/he.js +11 -1
  94. package/esm/langs/hu.d.ts +10 -0
  95. package/esm/langs/hu.js +11 -1
  96. package/esm/langs/id.d.ts +10 -0
  97. package/esm/langs/id.js +11 -1
  98. package/esm/langs/it.d.ts +10 -0
  99. package/esm/langs/it.js +11 -1
  100. package/esm/langs/ja.d.ts +10 -0
  101. package/esm/langs/ja.js +11 -1
  102. package/esm/langs/ko.d.ts +10 -0
  103. package/esm/langs/ko.js +11 -1
  104. package/esm/langs/mn.d.ts +10 -0
  105. package/esm/langs/mn.js +11 -1
  106. package/esm/langs/nl.d.ts +10 -0
  107. package/esm/langs/nl.js +11 -1
  108. package/esm/langs/no.d.ts +10 -0
  109. package/esm/langs/no.js +11 -1
  110. package/esm/langs/pl.d.ts +10 -0
  111. package/esm/langs/pl.js +11 -1
  112. package/esm/langs/pt_br.d.ts +10 -0
  113. package/esm/langs/pt_br.js +11 -1
  114. package/esm/langs/ru.d.ts +10 -0
  115. package/esm/langs/ru.js +11 -1
  116. package/esm/langs/sk.d.ts +10 -0
  117. package/esm/langs/sk.js +11 -1
  118. package/esm/langs/sv.d.ts +10 -0
  119. package/esm/langs/sv.js +11 -1
  120. package/esm/langs/tr.d.ts +10 -0
  121. package/esm/langs/tr.js +11 -1
  122. package/esm/langs/ua.d.ts +10 -0
  123. package/esm/langs/ua.js +11 -1
  124. package/esm/langs/zh_cn.d.ts +10 -0
  125. package/esm/langs/zh_cn.js +11 -1
  126. package/esm/langs/zh_tw.d.ts +10 -0
  127. package/esm/langs/zh_tw.js +11 -1
  128. package/esm/modules/dialog/dialog.d.ts +7 -0
  129. package/esm/modules/dialog/dialog.js +24 -6
  130. package/esm/modules/table/table.d.ts +6 -0
  131. package/esm/modules/table/table.js +18 -26
  132. package/esm/plugins/all.d.ts +1 -0
  133. package/esm/plugins/all.js +1 -0
  134. package/esm/plugins/backspace/cases/check-join-neighbors.js +90 -3
  135. package/esm/plugins/clean-html/clean-html.d.ts +8 -0
  136. package/esm/plugins/clean-html/clean-html.js +21 -1
  137. package/esm/plugins/clean-html/helpers/index.d.ts +1 -0
  138. package/esm/plugins/clean-html/helpers/index.js +1 -0
  139. package/esm/plugins/clean-html/helpers/remove-placeholder-br.d.ts +16 -0
  140. package/esm/plugins/clean-html/helpers/remove-placeholder-br.js +51 -0
  141. package/esm/plugins/delete/delete.d.ts +11 -1
  142. package/esm/plugins/delete/delete.js +92 -16
  143. package/esm/plugins/focus/focus.js +12 -1
  144. package/esm/plugins/iframe/config.d.ts +3 -0
  145. package/esm/plugins/iframe/config.js +0 -27
  146. package/esm/plugins/iframe/iframe.js +8 -1
  147. package/esm/plugins/image-processor/image-processor.d.ts +4 -1
  148. package/esm/plugins/image-processor/image-processor.js +15 -4
  149. package/esm/plugins/image-properties/readers/align.d.ts +2 -2
  150. package/esm/plugins/index.d.ts +1 -0
  151. package/esm/plugins/inline-popup/config/items/iframe.d.ts +9 -1
  152. package/esm/plugins/inline-popup/config/items/iframe.js +15 -0
  153. package/esm/plugins/inline-popup/inline-popup.d.ts +19 -0
  154. package/esm/plugins/inline-popup/inline-popup.js +70 -1
  155. package/esm/plugins/media/media.js +16 -7
  156. package/esm/plugins/ordered-list/config.js +13 -2
  157. package/esm/plugins/ordered-list/start-dialog.d.ts +19 -0
  158. package/esm/plugins/ordered-list/start-dialog.js +58 -0
  159. package/esm/plugins/placeholder/placeholder.js +7 -1
  160. package/esm/plugins/resizer/config.d.ts +5 -3
  161. package/esm/plugins/resizer/config.js +8 -2
  162. package/esm/plugins/resizer/resizer.d.ts +20 -0
  163. package/esm/plugins/resizer/resizer.js +140 -17
  164. package/esm/plugins/select-cells/select-cells.d.ts +23 -0
  165. package/esm/plugins/select-cells/select-cells.js +75 -1
  166. package/esm/plugins/table-keyboard-navigation/table-keyboard-navigation.js +3 -2
  167. package/esm/plugins/video/config.d.ts +6 -0
  168. package/esm/plugins/video/config.js +77 -65
  169. package/esm/plugins/video-properties/interface.d.ts +31 -0
  170. package/esm/plugins/video-properties/interface.js +6 -0
  171. package/esm/plugins/video-properties/readers.d.ts +26 -0
  172. package/esm/plugins/video-properties/readers.js +65 -0
  173. package/esm/plugins/video-properties/replace.d.ts +15 -0
  174. package/esm/plugins/video-properties/replace.js +97 -0
  175. package/esm/plugins/video-properties/ui/ui-video-form.d.ts +41 -0
  176. package/esm/plugins/video-properties/ui/ui-video-form.js +228 -0
  177. package/esm/plugins/video-properties/video-properties.d.ts +37 -0
  178. package/esm/plugins/video-properties/video-properties.js +175 -0
  179. package/esm/plugins/video-properties/writers.d.ts +15 -0
  180. package/esm/plugins/video-properties/writers.js +73 -0
  181. package/esm/types/types.d.ts +1 -1
  182. package/package.json +1 -1
  183. package/types/config.d.ts +28 -26
  184. package/types/core/helpers/html/word-lists.d.ts +29 -0
  185. package/types/core/helpers/utils/align.d.ts +1 -1
  186. package/types/core/selection/helpers/index.d.ts +1 -0
  187. package/types/core/selection/helpers/is-moved.d.ts +15 -0
  188. package/types/langs/ar.d.ts +10 -0
  189. package/types/langs/az.d.ts +10 -0
  190. package/types/langs/cs_cz.d.ts +10 -0
  191. package/types/langs/de.d.ts +10 -0
  192. package/types/langs/es.d.ts +10 -0
  193. package/types/langs/fa.d.ts +10 -0
  194. package/types/langs/fi.d.ts +10 -0
  195. package/types/langs/fr.d.ts +10 -0
  196. package/types/langs/he.d.ts +10 -0
  197. package/types/langs/hu.d.ts +10 -0
  198. package/types/langs/id.d.ts +10 -0
  199. package/types/langs/it.d.ts +10 -0
  200. package/types/langs/ja.d.ts +10 -0
  201. package/types/langs/ko.d.ts +10 -0
  202. package/types/langs/mn.d.ts +10 -0
  203. package/types/langs/nl.d.ts +10 -0
  204. package/types/langs/no.d.ts +10 -0
  205. package/types/langs/pl.d.ts +10 -0
  206. package/types/langs/pt_br.d.ts +10 -0
  207. package/types/langs/ru.d.ts +10 -0
  208. package/types/langs/sk.d.ts +10 -0
  209. package/types/langs/sv.d.ts +10 -0
  210. package/types/langs/tr.d.ts +10 -0
  211. package/types/langs/ua.d.ts +10 -0
  212. package/types/langs/zh_cn.d.ts +10 -0
  213. package/types/langs/zh_tw.d.ts +10 -0
  214. package/types/modules/dialog/dialog.d.ts +7 -0
  215. package/types/modules/table/table.d.ts +6 -0
  216. package/types/plugins/clean-html/clean-html.d.ts +8 -0
  217. package/types/plugins/clean-html/helpers/index.d.ts +1 -0
  218. package/types/plugins/clean-html/helpers/remove-placeholder-br.d.ts +16 -0
  219. package/types/plugins/delete/delete.d.ts +11 -1
  220. package/types/plugins/iframe/config.d.ts +3 -0
  221. package/types/plugins/image-processor/image-processor.d.ts +4 -1
  222. package/types/plugins/image-properties/readers/align.d.ts +2 -2
  223. package/types/plugins/index.d.ts +1 -0
  224. package/types/plugins/inline-popup/config/items/iframe.d.ts +9 -1
  225. package/types/plugins/inline-popup/inline-popup.d.ts +19 -0
  226. package/types/plugins/ordered-list/start-dialog.d.ts +19 -0
  227. package/types/plugins/resizer/config.d.ts +5 -3
  228. package/types/plugins/resizer/resizer.d.ts +20 -0
  229. package/types/plugins/select-cells/select-cells.d.ts +23 -0
  230. package/types/plugins/video/config.d.ts +6 -0
  231. package/types/plugins/video-properties/interface.d.ts +31 -0
  232. package/types/plugins/video-properties/readers.d.ts +26 -0
  233. package/types/plugins/video-properties/replace.d.ts +15 -0
  234. package/types/plugins/video-properties/ui/ui-video-form.d.ts +41 -0
  235. package/types/plugins/video-properties/video-properties.d.ts +37 -0
  236. package/types/plugins/video-properties/writers.d.ts +15 -0
  237. package/types/types/types.d.ts +1 -1
@@ -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
- import { LIST_TAGS } from "../../../core/constants.js";
6
+ import { INSEPARABLE_TAGS, LIST_TAGS } from "../../../core/constants.js";
7
7
  import { Dom } from "../../../core/dom/dom.js";
8
8
  import { getMoveFilter } from "../helpers.js";
9
9
  /**
@@ -21,6 +21,10 @@ export function checkJoinNeighbors(jodit, fakeNode, backspace) {
21
21
  }
22
22
  if (Dom.isElement(mainClosestBox) &&
23
23
  Dom.isContentEditable(mainClosestBox, jodit.editor)) {
24
+ // A table cell is never joined with its neighbour
25
+ if (Dom.up(fakeNode, Dom.isCell, mainClosestBox, true)) {
26
+ return false;
27
+ }
24
28
  const sibling = Dom.findNotEmptySibling(mainClosestBox, backspace);
25
29
  // A line break before the cursor is a single unit: remove it instead of
26
30
  // merging the current element into the (void) `<br>`, which used to move
@@ -30,15 +34,60 @@ export function checkJoinNeighbors(jodit, fakeNode, backspace) {
30
34
  jodit.s.setCursorBefore(fakeNode);
31
35
  return true;
32
36
  }
37
+ // Backspace leaves a block after a table alone, as Word does, rather
38
+ // than moving its text into the last cell
39
+ if (backspace && Dom.isTag(sibling, 'table')) {
40
+ jodit.s.setCursorBefore(fakeNode);
41
+ return true;
42
+ }
43
+ const box = findCaretBox(fakeNode, mainClosestBox);
33
44
  if (sibling &&
34
- (checkMoveListContent(jodit, mainClosestBox, sibling, backspace) ||
35
- moveContentAndRemoveEmpty(jodit, mainClosestBox, resolveTableSibling(sibling, backspace), backspace))) {
45
+ (checkPullNextLine(jodit, fakeNode, box, sibling, backspace) ||
46
+ checkMoveListContent(jodit, box, sibling, backspace) ||
47
+ moveContentAndRemoveEmpty(jodit, box, findEdgeBlock(resolveTableSibling(sibling, backspace), backspace, jodit.editor), backspace))) {
36
48
  jodit.s.setCursorBefore(fakeNode);
37
49
  return true;
38
50
  }
39
51
  }
40
52
  return false;
41
53
  }
54
+ /**
55
+ * The block holding the caret inside the box that has the sibling, so
56
+ * `<p>one</p><div><p>|two</p></div>` joins the two paragraphs instead of
57
+ * moving the whole `<div>` into the first one. For a list item that is its
58
+ * list, which the list cases handle.
59
+ */
60
+ function findCaretBox(fakeNode, mainClosestBox) {
61
+ const block = Dom.up(fakeNode, Dom.isBlock, mainClosestBox);
62
+ if (!block) {
63
+ return mainClosestBox;
64
+ }
65
+ return Dom.isLeaf(block) && block.parentElement
66
+ ? block.parentElement
67
+ : block;
68
+ }
69
+ /**
70
+ * The innermost block on the sibling's edge facing the caret, so the
71
+ * content is joined to text: `<div><p>one</p></div><p>|two</p>` gives
72
+ * `<div><p>one|two</p></div>`
73
+ */
74
+ function findEdgeBlock(sibling, backspace, root) {
75
+ let block = sibling;
76
+ while (block) {
77
+ const edge = backspace ? block.lastChild : block.firstChild;
78
+ const child = edge && Dom.isEmptyTextNode(edge)
79
+ ? Dom.findNotEmptySibling(edge, backspace)
80
+ : edge;
81
+ if (!Dom.isBlock(child) ||
82
+ Dom.isTag(child, INSEPARABLE_TAGS) ||
83
+ Dom.isTag(child, 'table') ||
84
+ !Dom.isContentEditable(child, root)) {
85
+ return block;
86
+ }
87
+ block = child;
88
+ }
89
+ return block;
90
+ }
42
91
  /**
43
92
  * Content cannot be merged into the `<table>` element itself — it would land
44
93
  * between the table sections (after `</tbody>`), which is invalid HTML and
@@ -56,6 +105,44 @@ function resolveTableSibling(sibling, backspace) {
56
105
  });
57
106
  return cells.length ? cells[backspace ? cells.length - 1 : 0] : null;
58
107
  }
108
+ /**
109
+ * Delete at the edge of a list pulls the next line up to the caret, as the
110
+ * browsers do, instead of moving the caret's line into the next block:
111
+ * `<ul><li>one|</li></ul><p>two</p>` gives `<ul><li>one|two</li></ul>`, and
112
+ * `<p>one|</p><ul><li>two</li></ul>` gives `<p>one|two</p>`
113
+ * @private
114
+ */
115
+ function checkPullNextLine(jodit, fakeNode, box, sibling, backspace) {
116
+ if (backspace || !(Dom.isList(box) || Dom.isList(sibling))) {
117
+ return false;
118
+ }
119
+ const target = Dom.up(fakeNode, Dom.isBlock, box, true);
120
+ const source = Dom.isList(sibling) ? sibling.firstElementChild : sibling;
121
+ if (!target ||
122
+ !source ||
123
+ Dom.isTag(source, INSEPARABLE_TAGS) ||
124
+ Dom.isTag(source, 'table')) {
125
+ return false;
126
+ }
127
+ if (!Dom.isEmpty(source)) {
128
+ const line = [];
129
+ for (let node = source.firstChild; node && !Dom.isBlock(node); node = node.nextSibling) {
130
+ line.push(node);
131
+ }
132
+ if (!line.length) {
133
+ return false;
134
+ }
135
+ const filter = getMoveFilter(jodit);
136
+ line.forEach(node => filter(node) ? Dom.append(target, node) : Dom.safeRemove(node));
137
+ }
138
+ let remove = source;
139
+ while (remove && remove !== jodit.editor && Dom.isEmpty(remove)) {
140
+ const parent = remove.parentElement;
141
+ Dom.safeRemove(remove);
142
+ remove = parent;
143
+ }
144
+ return true;
145
+ }
59
146
  function checkMoveListContent(jodit, mainClosestBox, sibling, backspace) {
60
147
  // Process UL/LI/OL cases
61
148
  const siblingIsList = Dom.isTag(sibling, LIST_TAGS);
@@ -23,6 +23,14 @@ export declare class cleanHtml extends Plugin {
23
23
  */
24
24
  protected onChangeCleanHTML(): void;
25
25
  private currentSelectionNode;
26
+ /**
27
+ * Typing into an empty block (`<p><br></p>`) replaces its `<br>`, but the
28
+ * browser keeps the `<br>` when the caret is in an invisible-space text
29
+ * node in front of it, as Jodit leaves the caret in many of the empty
30
+ * blocks it makes (a cleared editor, a heading in an empty table cell).
31
+ * See #1632
32
+ */
33
+ protected onInputRemovePlaceholderBr(event: InputEvent | CompositionEvent): void;
26
34
  private walker;
27
35
  protected startWalker(): void;
28
36
  protected beforeCommand(command: string): void | false;
@@ -21,7 +21,7 @@ import { safeHTML } from "../../core/helpers/html/safe-html.js";
21
21
  import { Plugin } from "../../core/plugin/plugin.js";
22
22
  import "./config.js";
23
23
  import { replaceOldTags } from "./helpers/visitor/filters/replace-old-tags.js";
24
- import { getHash, removeFormatForCollapsedSelection, removeFormatForSelection, visitNodeWalker } from "./helpers/index.js";
24
+ import { getHash, removeFormatForCollapsedSelection, removeFormatForSelection, removePlaceholderBr, visitNodeWalker } from "./helpers/index.js";
25
25
  /**
26
26
  * Clean HTML after removeFormat and insertHorizontalRule command
27
27
  */
@@ -72,6 +72,23 @@ export class cleanHtml extends Plugin {
72
72
  this.walker.setWork(editor.editor);
73
73
  this.currentSelectionNode = editor.s.current();
74
74
  }
75
+ /**
76
+ * Typing into an empty block (`<p><br></p>`) replaces its `<br>`, but the
77
+ * browser keeps the `<br>` when the caret is in an invisible-space text
78
+ * node in front of it, as Jodit leaves the caret in many of the empty
79
+ * blocks it makes (a cleared editor, a heading in an empty table cell).
80
+ * See #1632
81
+ */
82
+ onInputRemovePlaceholderBr(event) {
83
+ var _a;
84
+ if (!this.isEditMode ||
85
+ (event.type === 'input' &&
86
+ (event.isComposing ||
87
+ !((_a = event.inputType) === null || _a === void 0 ? void 0 : _a.startsWith('insert'))))) {
88
+ return;
89
+ }
90
+ removePlaceholderBr(this.j);
91
+ }
75
92
  startWalker() {
76
93
  const { jodit } = this;
77
94
  const allow = getHash(this.j.o.cleanHTML.allowTags);
@@ -239,6 +256,9 @@ export class cleanHtml extends Plugin {
239
256
  __decorate([
240
257
  watch([':change', ':afterSetMode', ':afterInit', ':mousedown', ':keydown'])
241
258
  ], cleanHtml.prototype, "onChangeCleanHTML", null);
259
+ __decorate([
260
+ watch([':input', 'jodit.editor:compositionend'])
261
+ ], cleanHtml.prototype, "onInputRemovePlaceholderBr", null);
242
262
  __decorate([
243
263
  hook('ready')
244
264
  ], cleanHtml.prototype, "startWalker", null);
@@ -10,4 +10,5 @@ export * from "./get-hash";
10
10
  export * from "./is-allowed-media-embed";
11
11
  export * from "./remove-format/remove-format-for-collapsed-selection";
12
12
  export * from "./remove-format/remove-format-for-selection";
13
+ export * from "./remove-placeholder-br";
13
14
  export * from "./visitor/visit-node-walker";
@@ -10,4 +10,5 @@ export * from "./get-hash.js";
10
10
  export * from "./is-allowed-media-embed.js";
11
11
  export * from "./remove-format/remove-format-for-collapsed-selection.js";
12
12
  export * from "./remove-format/remove-format-for-selection.js";
13
+ export * from "./remove-placeholder-br.js";
13
14
  export * from "./visitor/visit-node-walker.js";
@@ -0,0 +1,16 @@
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 plugins/clean-html
8
+ */
9
+ import type { IJodit } from "../../../types/index";
10
+ /**
11
+ * Remove the `<br>` that kept the block with the caret open while it was
12
+ * empty, once text has gone into the block: when the block is one line of
13
+ * text that ends in that `<br>`, the `<br>` adds nothing to it
14
+ * @private
15
+ */
16
+ export declare function removePlaceholderBr(jodit: IJodit): void;
@@ -0,0 +1,51 @@
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 { INSEPARABLE_TAGS } from "../../../core/constants.js";
7
+ import { Dom } from "../../../core/dom/dom.js";
8
+ import { trim } from "../../../core/helpers/string/trim.js";
9
+ function lastNode(node) {
10
+ let last = node.lastChild;
11
+ while (Dom.isText(last) && !last.nodeValue) {
12
+ last = last.previousSibling;
13
+ }
14
+ return last;
15
+ }
16
+ /**
17
+ * Remove the `<br>` that kept the block with the caret open while it was
18
+ * empty, once text has gone into the block: when the block is one line of
19
+ * text that ends in that `<br>`, the `<br>` adds nothing to it
20
+ * @private
21
+ */
22
+ export function removePlaceholderBr(jodit) {
23
+ var _a;
24
+ const block = Dom.closest(jodit.s.current(), Dom.isBlock, jodit.editor);
25
+ if (!block || !trim((_a = block.textContent) !== null && _a !== void 0 ? _a : '')) {
26
+ return;
27
+ }
28
+ let br = lastNode(block);
29
+ while (Dom.isElement(br) &&
30
+ !Dom.isTag(br, INSEPARABLE_TAGS) &&
31
+ !Dom.isBlock(br)) {
32
+ br = lastNode(br);
33
+ }
34
+ if (!Dom.isTag(br, 'br')) {
35
+ return;
36
+ }
37
+ // Another line break, a block, an image or an embed means the `<br>` may
38
+ // end an empty line, or give the caret a place after the embed
39
+ const isOneLineOfText = Dom.each(block, (node) => {
40
+ if (node !== br &&
41
+ Dom.isElement(node) &&
42
+ (Dom.isTag(node, INSEPARABLE_TAGS) ||
43
+ Dom.isBlock(node) ||
44
+ (Dom.isHTMLElement(node) && !node.isContentEditable))) {
45
+ return false;
46
+ }
47
+ });
48
+ if (isOneLineOfText) {
49
+ Dom.safeRemove(br);
50
+ }
51
+ }
@@ -20,13 +20,23 @@ export declare class deleteCommand extends Plugin {
20
20
  */
21
21
  private __afterDeleteCommand;
22
22
  private __onDeleteCommand;
23
+ /**
24
+ * A selection that starts or ends in a table cell clears the selected part
25
+ * of each cell it covers and keeps the cells, as the browsers do.
26
+ * `deleteContents` would remove the cells it fully covers, and the cells
27
+ * it cuts would then be merged as blocks. The range is moved out of those
28
+ * tables, so only the content between them is left to delete.
29
+ *
30
+ * Returns where the caret goes, or null when no table is involved
31
+ */
32
+ private __clearSelectedCells;
23
33
  private __moveContentInLeftSibling;
24
34
  /**
25
35
  * If left sibling is list - return last leaf
26
36
  */
27
37
  private __defineRightLeftBox;
28
38
  /**
29
- * Add BR in empty blocks left and right(for table cell)
39
+ * Add BR in the empty block left at the caret
30
40
  */
31
41
  private __addBrInEmptyBlock;
32
42
  private __moveCursorInEditableSibling;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { Dom } from "../../core/dom/index.js";
7
7
  import { pluginSystem } from "../../core/global.js";
8
- import { $$ } from "../../core/helpers/index.js";
8
+ import { toArray } from "../../core/helpers/index.js";
9
9
  import { trim } from "../../core/helpers/string/trim.js";
10
10
  import { Plugin } from "../../core/plugin/index.js";
11
11
  import "./interface.js";
@@ -56,6 +56,11 @@ export class deleteCommand extends Plugin {
56
56
  }
57
57
  jodit.s.expandSelection();
58
58
  const range = jodit.s.range;
59
+ const caret = this.__clearSelectedCells(range);
60
+ if (caret && range.collapsed) {
61
+ jodit.s.selectRange(caret);
62
+ return false;
63
+ }
59
64
  range.deleteContents();
60
65
  const fake = jodit.createInside.fake();
61
66
  range.insertNode(fake);
@@ -65,13 +70,78 @@ export class deleteCommand extends Plugin {
65
70
  range.setStartBefore(fake);
66
71
  range.collapse(true);
67
72
  this.__moveCursorInEditableSibling(jodit, leftSibling, fake, range);
68
- this.__addBrInEmptyBlock(fake, rightSibling, range);
73
+ this.__addBrInEmptyBlock(fake, range);
69
74
  Dom.safeRemove(fake);
70
- jodit.s.selectRange(range);
75
+ jodit.s.selectRange(caret !== null && caret !== void 0 ? caret : range);
71
76
  return false;
72
77
  }
78
+ /**
79
+ * A selection that starts or ends in a table cell clears the selected part
80
+ * of each cell it covers and keeps the cells, as the browsers do.
81
+ * `deleteContents` would remove the cells it fully covers, and the cells
82
+ * it cuts would then be merged as blocks. The range is moved out of those
83
+ * tables, so only the content between them is left to delete.
84
+ *
85
+ * Returns where the caret goes, or null when no table is involved
86
+ */
87
+ __clearSelectedCells(range) {
88
+ const { editor, createInside } = this.j;
89
+ const startCell = Dom.closest(range.startContainer, Dom.isCell, editor);
90
+ const endCell = Dom.closest(range.endContainer, Dom.isCell, editor);
91
+ if (startCell === endCell) {
92
+ return null;
93
+ }
94
+ const startTable = Dom.closest(startCell, 'table', editor);
95
+ const endTable = Dom.closest(endCell, 'table', editor);
96
+ const caret = range.cloneRange();
97
+ caret.collapse(true);
98
+ const parts = [];
99
+ const addParts = (table) => {
100
+ toArray(table.rows)
101
+ .flatMap(row => toArray(row.cells))
102
+ .filter(cell => range.intersectsNode(cell))
103
+ .forEach(cell => {
104
+ const part = range.cloneRange();
105
+ if (!Dom.isOrContains(cell, range.startContainer)) {
106
+ part.setStart(cell, 0);
107
+ }
108
+ if (!Dom.isOrContains(cell, range.endContainer)) {
109
+ part.setEnd(cell, cell.childNodes.length);
110
+ }
111
+ parts.push([cell, part]);
112
+ });
113
+ };
114
+ if (startTable) {
115
+ addParts(startTable);
116
+ if (startTable === endTable) {
117
+ range.collapse(true);
118
+ }
119
+ else {
120
+ range.setStartAfter(startTable);
121
+ }
122
+ }
123
+ if (endTable && endTable !== startTable) {
124
+ addParts(endTable);
125
+ range.setEndBefore(endTable);
126
+ }
127
+ parts.forEach(([cell, part]) => {
128
+ part.deleteContents();
129
+ if (Dom.isEmpty(cell)) {
130
+ Dom.detach(cell);
131
+ Dom.append(cell, createInside.element('br'));
132
+ }
133
+ });
134
+ return startTable ? caret : null;
135
+ }
73
136
  __moveContentInLeftSibling(fake, leftSibling, rightSibling) {
74
137
  leftSibling = this.__defineRightLeftBox(leftSibling);
138
+ // Nothing is moved into a table, and a block left empty after it goes
139
+ if (Dom.isTag(leftSibling, 'table')) {
140
+ if (Dom.isBlock(rightSibling) && Dom.isEmpty(rightSibling)) {
141
+ Dom.safeRemove(rightSibling);
142
+ }
143
+ return;
144
+ }
75
145
  if (!Dom.isList(rightSibling) &&
76
146
  !Dom.isTag(rightSibling, 'table') &&
77
147
  Dom.isBlock(rightSibling) &&
@@ -80,11 +150,24 @@ export class deleteCommand extends Plugin {
80
150
  Dom.moveContent(rightSibling, leftSibling);
81
151
  Dom.safeRemove(rightSibling);
82
152
  }
83
- // Remove empty right LI
84
153
  if (Dom.isList(rightSibling) &&
85
- Dom.isLeaf(rightSibling.firstElementChild) &&
86
- Dom.isEmpty(rightSibling.firstElementChild)) {
87
- Dom.safeRemove(rightSibling.firstElementChild);
154
+ Dom.isLeaf(rightSibling.firstElementChild)) {
155
+ const item = rightSibling.firstElementChild;
156
+ // What's left of the right list's first item joins the left block,
157
+ // up to a list nested in the item
158
+ if (!Dom.isEmpty(item) && Dom.isBlock(leftSibling)) {
159
+ Dom.append(leftSibling, fake);
160
+ while (item.firstChild && !Dom.isBlock(item.firstChild)) {
161
+ Dom.append(leftSibling, item.firstChild);
162
+ }
163
+ }
164
+ // Remove empty right LI
165
+ if (Dom.isEmpty(item)) {
166
+ Dom.safeRemove(item);
167
+ }
168
+ if (Dom.isEmpty(rightSibling)) {
169
+ Dom.safeRemove(rightSibling);
170
+ }
88
171
  }
89
172
  }
90
173
  /**
@@ -102,9 +185,9 @@ export class deleteCommand extends Plugin {
102
185
  return lastLeaf;
103
186
  }
104
187
  /**
105
- * Add BR in empty blocks left and right(for table cell)
188
+ * Add BR in the empty block left at the caret
106
189
  */
107
- __addBrInEmptyBlock(fake, rightSibling, range) {
190
+ __addBrInEmptyBlock(fake, range) {
108
191
  const jodit = this.j;
109
192
  if (fake.isConnected &&
110
193
  Dom.isBlock(fake.parentNode) &&
@@ -115,13 +198,6 @@ export class deleteCommand extends Plugin {
115
198
  range.setStartBefore(br);
116
199
  range.collapse(true);
117
200
  }
118
- // Add BR in the right empty table cell
119
- if (Dom.isTag(rightSibling, 'table')) {
120
- const firstCell = $$('td,th', rightSibling).shift();
121
- if (Dom.isCell(firstCell) && Dom.isEmpty(firstCell)) {
122
- Dom.append(firstCell, jodit.createInside.element('br'));
123
- }
124
- }
125
201
  }
126
202
  __moveCursorInEditableSibling(jodit, leftSibling, fake, range) {
127
203
  var _a;
@@ -67,7 +67,18 @@ export function focus(editor) {
67
67
  editor.e.on('afterInit', () => {
68
68
  if (editor.o.autofocus) {
69
69
  if (editor.defaultTimeout) {
70
- editor.async.setTimeout(focus, 300);
70
+ // A click or key in the editor during the delay has put the
71
+ // caret where the user wants it, so autofocus leaves it there
72
+ const userInput = 'mousedown touchstart keydown';
73
+ const cancel = () => {
74
+ editor.async.clearTimeout(timeout);
75
+ editor.e.off(userInput, cancel);
76
+ };
77
+ const timeout = editor.async.setTimeout(() => {
78
+ editor.e.off(userInput, cancel);
79
+ focus();
80
+ }, 300);
81
+ editor.e.on(userInput, cancel);
71
82
  }
72
83
  else {
73
84
  focus();
@@ -37,6 +37,9 @@ declare module 'jodit/config' {
37
37
  iframeDoctype: string;
38
38
  /**
39
39
  * Custom style to be used inside the iframe to display content.
40
+ * It goes in after `iframeCSSLinks`. The rules Jodit's own plugins need
41
+ * in the editing document are added apart from it, so a value of your
42
+ * own doesn't have to repeat them.
40
43
  *
41
44
  * ```javascript
42
45
  * Jodit.make('#editor', {
@@ -51,33 +51,6 @@ Config.prototype.iframeStyle =
51
51
  'p{' +
52
52
  'margin-top:0;' +
53
53
  '}' +
54
- '[data-jodit_iframe_wrapper]{' +
55
- 'display: block;' +
56
- 'user-select: none;' +
57
- 'position: relative;' +
58
- '}' +
59
- '[data-jodit_iframe_wrapper] iframe{' +
60
- 'position: relative;' +
61
- '}' +
62
- '[data-jodit_iframe_wrapper]:after {' +
63
- 'position:absolute;' +
64
- 'content:"";' +
65
- 'z-index:1;' +
66
- 'top:0;' +
67
- 'left:0;' +
68
- 'right: 0;' +
69
- 'bottom: 0;' +
70
- 'cursor: pointer;' +
71
- 'display: block;' +
72
- 'background: rgba(0, 0, 0, 0);' +
73
- '} ' +
74
- '[data-jodit_iframe_wrapper][data-jodit-wrapper_active=true] iframe{' +
75
- 'z-index: 2;' +
76
- '}' +
77
- '.jodit-wysiwyg_image-selected::selection,' +
78
- '.jodit-wysiwyg_image-selected *:not(img)::selection{' +
79
- 'background:transparent' +
80
- '}' +
81
54
  '.jodit_disabled{' +
82
55
  'user-select: none;' +
83
56
  '-o-user-select: none;' +
@@ -7,7 +7,7 @@ import { MODE_SOURCE } from "../../core/constants.js";
7
7
  import { Dom } from "../../core/dom/dom.js";
8
8
  import { pluginSystem } from "../../core/global.js";
9
9
  import { error } from "../../core/helpers/index.js";
10
- import { attr, callPromise, css, defaultLanguage } from "../../core/helpers/index.js";
10
+ import { $$, attr, callPromise, css, defaultLanguage } from "../../core/helpers/index.js";
11
11
  import "./config.js";
12
12
  /**
13
13
  * Iframe plugin - use `iframe` instead of DIV in editor. It can be need when you want to attach custom styles in editor
@@ -43,6 +43,8 @@ export function iframe(editor) {
43
43
  attr(link, 'rel', 'stylesheet');
44
44
  attr(link, 'href', href);
45
45
  doc.head && Dom.append(doc.head, link);
46
+ // What plugins measured in the editing document changes once it's styled
47
+ jodit.e.on(link, 'load', () => jodit.e.fire('resize'));
46
48
  });
47
49
  }
48
50
  if (opt.iframeStyle) {
@@ -83,6 +85,8 @@ export function iframe(editor) {
83
85
  null);
84
86
  };
85
87
  const clearMarkers = (html) => {
88
+ // The plugins' own styles, from `getContainer`, aren't part of the value
89
+ html = html.replace(/<style[^>]+jodit-box[^>]*>[\s\S]*?<\/style>/gi, '');
86
90
  const bodyReg = /<body.*<\/body>/im, bodyMarker = '{%%BODY%%}', body = bodyReg.exec(html);
87
91
  if (body) {
88
92
  // remove markers
@@ -119,7 +123,10 @@ export function iframe(editor) {
119
123
  const replaceDocument = (html) => {
120
124
  const inert = new DOMParser().parseFromString(html, 'text/html');
121
125
  editor.e.fire('safeHTML', inert.documentElement);
126
+ // The plugins' own styles stay, as the value leaves them out
127
+ const styles = $$('style.jodit-box', doc.head);
122
128
  doc.replaceChild(doc.adoptNode(inert.documentElement), doc.documentElement);
129
+ styles.forEach(style => Dom.append(doc.head, style));
123
130
  };
124
131
  if (docMode) {
125
132
  const tag = editor.element.tagName;
@@ -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: {
@@ -16,20 +16,26 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
16
16
  import { INVISIBLE_SPACE_REG_EXP, SOURCE_CONSUMER } from "../../core/constants.js";
17
17
  import { autobind, cached, debounce, watch } from "../../core/decorators/index.js";
18
18
  import { Dom } from "../../core/dom/dom.js";
19
- import { pluginSystem } from "../../core/global.js";
20
- import { $$, dataBind } from "../../core/helpers/index.js";
19
+ import { getContainer, pluginSystem } from "../../core/global.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
  */
30
31
  export class imageProcessor extends Plugin {
31
32
  afterInit(jodit) {
32
33
  jodit.e.on(jodit.ed, 'selectionchange', this.onSelectionChange);
34
+ // Over the highlight in `styles/reset.less`; the image keeps its own. It goes
35
+ // in the editing document, so iframe mode has it whatever `iframeStyle` holds
36
+ getContainer(jodit, 'image-processor', 'style', true).textContent =
37
+ `.jodit-wysiwyg.${IMAGE_SELECTED}::selection,` +
38
+ `.jodit-wysiwyg.${IMAGE_SELECTED} *:not(img)::selection{background:transparent}`;
33
39
  }
34
40
  beforeDestruct(jodit) {
35
41
  jodit.e.off(jodit.ed, 'selectionchange', this.onSelectionChange);
@@ -66,7 +72,10 @@ export class imageProcessor extends Plugin {
66
72
  }
67
73
  /**
68
74
  * 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
75
+ * paints the rest of a block image's line with its parent's highlight. See #1528.
76
+ * Media in a wrapper (`jodit-media`, the resizer's `jodit` for an embed, or
77
+ * one from `mediaWrappers`) isn't highlighted either, as Safari paints its
78
+ * wrapper's whole line, and its resize frame shows it's selected
70
79
  */
71
80
  onSelectionChange() {
72
81
  const { jodit } = this;
@@ -117,9 +126,11 @@ function isImageSelected(jodit) {
117
126
  return false;
118
127
  }
119
128
  const { startContainer, startOffset, endContainer, endOffset } = sel.getRangeAt(0);
129
+ const node = startContainer.childNodes[startOffset];
120
130
  return (startContainer === endContainer &&
121
131
  endOffset === startOffset + 1 &&
122
- Dom.isTag(startContainer.childNodes[startOffset], 'img') &&
132
+ (Dom.isTag(node, SELECTED_TAGS) ||
133
+ Boolean(getWrappedMedia(jodit, node))) &&
123
134
  Dom.isOrContains(jodit.editor, startContainer));
124
135
  }
125
136
  /**
@@ -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;