jodit 4.18.4 → 4.18.7

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 (209) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/es2015/jodit.css +13 -34
  3. package/es2015/jodit.fat.min.css +1 -1
  4. package/es2015/jodit.fat.min.js +12 -12
  5. package/es2015/jodit.js +1130 -225
  6. package/es2015/jodit.min.css +1 -1
  7. package/es2015/jodit.min.js +12 -12
  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 +12 -12
  16. package/es2018/jodit.min.css +1 -1
  17. package/es2018/jodit.min.js +12 -12
  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 +13 -34
  21. package/es2021/jodit.fat.min.css +1 -1
  22. package/es2021/jodit.fat.min.js +16 -16
  23. package/es2021/jodit.js +1121 -223
  24. package/es2021/jodit.min.css +1 -1
  25. package/es2021/jodit.min.js +16 -16
  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 +13 -34
  33. package/es2021.en/jodit.fat.min.css +1 -1
  34. package/es2021.en/jodit.fat.min.js +16 -16
  35. package/es2021.en/jodit.js +1095 -197
  36. package/es2021.en/jodit.min.css +1 -1
  37. package/es2021.en/jodit.min.js +16 -16
  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 +14 -41
  45. package/es5/jodit.fat.min.css +1 -1
  46. package/es5/jodit.fat.min.js +2 -2
  47. package/es5/jodit.js +1260 -273
  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 +5 -0
  60. package/esm/core/constants.js +1 -1
  61. package/esm/core/decorators/component/component.js +12 -0
  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/selection/helpers/index.d.ts +1 -0
  68. package/esm/core/selection/helpers/index.js +1 -0
  69. package/esm/core/selection/helpers/is-moved.d.ts +15 -0
  70. package/esm/core/selection/helpers/is-moved.js +22 -0
  71. package/esm/core/selection/style/api/list/toggle-ordered-list.js +40 -3
  72. package/esm/core/selection/style/transactions.js +5 -2
  73. package/esm/core/ui/popup/popup.js +14 -9
  74. package/esm/jodit.js +5 -1
  75. package/esm/langs/ar.d.ts +3 -0
  76. package/esm/langs/ar.js +4 -1
  77. package/esm/langs/az.d.ts +3 -0
  78. package/esm/langs/az.js +4 -1
  79. package/esm/langs/cs_cz.d.ts +3 -0
  80. package/esm/langs/cs_cz.js +4 -1
  81. package/esm/langs/de.d.ts +3 -0
  82. package/esm/langs/de.js +4 -1
  83. package/esm/langs/es.d.ts +3 -0
  84. package/esm/langs/es.js +4 -1
  85. package/esm/langs/fa.d.ts +3 -0
  86. package/esm/langs/fa.js +4 -1
  87. package/esm/langs/fi.d.ts +3 -0
  88. package/esm/langs/fi.js +4 -1
  89. package/esm/langs/fr.d.ts +3 -0
  90. package/esm/langs/fr.js +4 -1
  91. package/esm/langs/he.d.ts +3 -0
  92. package/esm/langs/he.js +4 -1
  93. package/esm/langs/hu.d.ts +3 -0
  94. package/esm/langs/hu.js +4 -1
  95. package/esm/langs/id.d.ts +3 -0
  96. package/esm/langs/id.js +4 -1
  97. package/esm/langs/it.d.ts +3 -0
  98. package/esm/langs/it.js +4 -1
  99. package/esm/langs/ja.d.ts +3 -0
  100. package/esm/langs/ja.js +4 -1
  101. package/esm/langs/ko.d.ts +3 -0
  102. package/esm/langs/ko.js +4 -1
  103. package/esm/langs/mn.d.ts +3 -0
  104. package/esm/langs/mn.js +4 -1
  105. package/esm/langs/nl.d.ts +3 -0
  106. package/esm/langs/nl.js +4 -1
  107. package/esm/langs/no.d.ts +3 -0
  108. package/esm/langs/no.js +4 -1
  109. package/esm/langs/pl.d.ts +3 -0
  110. package/esm/langs/pl.js +4 -1
  111. package/esm/langs/pt_br.d.ts +3 -0
  112. package/esm/langs/pt_br.js +4 -1
  113. package/esm/langs/ru.d.ts +3 -0
  114. package/esm/langs/ru.js +4 -1
  115. package/esm/langs/sk.d.ts +3 -0
  116. package/esm/langs/sk.js +4 -1
  117. package/esm/langs/sv.d.ts +3 -0
  118. package/esm/langs/sv.js +4 -1
  119. package/esm/langs/tr.d.ts +3 -0
  120. package/esm/langs/tr.js +4 -1
  121. package/esm/langs/ua.d.ts +3 -0
  122. package/esm/langs/ua.js +4 -1
  123. package/esm/langs/zh_cn.d.ts +3 -0
  124. package/esm/langs/zh_cn.js +4 -1
  125. package/esm/langs/zh_tw.d.ts +3 -0
  126. package/esm/langs/zh_tw.js +4 -1
  127. package/esm/modules/dialog/dialog.d.ts +7 -0
  128. package/esm/modules/dialog/dialog.js +24 -6
  129. package/esm/modules/file-browser/ui/files/files.js +17 -2
  130. package/esm/modules/file-browser/ui/tree/tree.js +17 -2
  131. package/esm/modules/table/table.d.ts +6 -0
  132. package/esm/modules/table/table.js +18 -26
  133. package/esm/plugins/backspace/cases/check-join-neighbors.js +90 -3
  134. package/esm/plugins/backspace/cases/check-remove-char.d.ts +2 -1
  135. package/esm/plugins/backspace/cases/check-remove-char.js +25 -4
  136. package/esm/plugins/backspace/config.d.ts +2 -0
  137. package/esm/plugins/backspace/config.js +6 -3
  138. package/esm/plugins/clean-html/clean-html.d.ts +8 -0
  139. package/esm/plugins/clean-html/clean-html.js +21 -1
  140. package/esm/plugins/clean-html/helpers/index.d.ts +1 -0
  141. package/esm/plugins/clean-html/helpers/index.js +1 -0
  142. package/esm/plugins/clean-html/helpers/remove-placeholder-br.d.ts +16 -0
  143. package/esm/plugins/clean-html/helpers/remove-placeholder-br.js +51 -0
  144. package/esm/plugins/delete/delete.d.ts +11 -1
  145. package/esm/plugins/delete/delete.js +92 -16
  146. package/esm/plugins/focus/focus.js +12 -1
  147. package/esm/plugins/iframe/config.d.ts +3 -0
  148. package/esm/plugins/iframe/config.js +0 -27
  149. package/esm/plugins/iframe/iframe.js +8 -1
  150. package/esm/plugins/image-processor/image-processor.js +6 -1
  151. package/esm/plugins/image-properties/image-properties.d.ts +5 -0
  152. package/esm/plugins/image-properties/image-properties.js +30 -5
  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/mobile/mobile.js +7 -4
  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/resizer.d.ts +10 -0
  161. package/esm/plugins/resizer/resizer.js +76 -2
  162. package/esm/plugins/select-cells/select-cells.d.ts +23 -0
  163. package/esm/plugins/select-cells/select-cells.js +75 -1
  164. package/esm/plugins/table-keyboard-navigation/table-keyboard-navigation.js +3 -2
  165. package/package.json +1 -1
  166. package/types/config.d.ts +5 -0
  167. package/types/core/helpers/html/word-lists.d.ts +29 -0
  168. package/types/core/selection/helpers/index.d.ts +1 -0
  169. package/types/core/selection/helpers/is-moved.d.ts +15 -0
  170. package/types/langs/ar.d.ts +3 -0
  171. package/types/langs/az.d.ts +3 -0
  172. package/types/langs/cs_cz.d.ts +3 -0
  173. package/types/langs/de.d.ts +3 -0
  174. package/types/langs/es.d.ts +3 -0
  175. package/types/langs/fa.d.ts +3 -0
  176. package/types/langs/fi.d.ts +3 -0
  177. package/types/langs/fr.d.ts +3 -0
  178. package/types/langs/he.d.ts +3 -0
  179. package/types/langs/hu.d.ts +3 -0
  180. package/types/langs/id.d.ts +3 -0
  181. package/types/langs/it.d.ts +3 -0
  182. package/types/langs/ja.d.ts +3 -0
  183. package/types/langs/ko.d.ts +3 -0
  184. package/types/langs/mn.d.ts +3 -0
  185. package/types/langs/nl.d.ts +3 -0
  186. package/types/langs/no.d.ts +3 -0
  187. package/types/langs/pl.d.ts +3 -0
  188. package/types/langs/pt_br.d.ts +3 -0
  189. package/types/langs/ru.d.ts +3 -0
  190. package/types/langs/sk.d.ts +3 -0
  191. package/types/langs/sv.d.ts +3 -0
  192. package/types/langs/tr.d.ts +3 -0
  193. package/types/langs/ua.d.ts +3 -0
  194. package/types/langs/zh_cn.d.ts +3 -0
  195. package/types/langs/zh_tw.d.ts +3 -0
  196. package/types/modules/dialog/dialog.d.ts +7 -0
  197. package/types/modules/table/table.d.ts +6 -0
  198. package/types/plugins/backspace/cases/check-remove-char.d.ts +2 -1
  199. package/types/plugins/backspace/config.d.ts +2 -0
  200. package/types/plugins/clean-html/clean-html.d.ts +8 -0
  201. package/types/plugins/clean-html/helpers/index.d.ts +1 -0
  202. package/types/plugins/clean-html/helpers/remove-placeholder-br.d.ts +16 -0
  203. package/types/plugins/delete/delete.d.ts +11 -1
  204. package/types/plugins/iframe/config.d.ts +3 -0
  205. package/types/plugins/image-properties/image-properties.d.ts +5 -0
  206. package/types/plugins/inline-popup/inline-popup.d.ts +19 -0
  207. package/types/plugins/ordered-list/start-dialog.d.ts +19 -0
  208. package/types/plugins/resizer/resizer.d.ts +10 -0
  209. package/types/plugins/select-cells/select-cells.d.ts +23 -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.4
4
+ * Version: v4.18.7
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.4
4
+ * Version: v4.18.7
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.4
4
+ * Version: v4.18.7
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.4
4
+ * Version: v4.18.7
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.4
4
+ * Version: v4.18.7
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.4
4
+ * Version: v4.18.7
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.4
4
+ * Version: v4.18.7
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.4
4
+ * Version: v4.18.7
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.4
4
+ * Version: v4.18.7
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
package/esm/config.d.ts CHANGED
@@ -1034,6 +1034,8 @@ interface Config {
1034
1034
  interface Config {
1035
1035
  /**
1036
1036
  * Keyboard hotkey mappings for delete and backspace operations (character, word, sentence).
1037
+ * On macOS the defaults leave out Cmd+Backspace, Ctrl+Backspace, Ctrl+Option+Backspace
1038
+ * and Cmd+Option+Backspace, so the browser applies the system's own bindings to them.
1037
1039
  */
1038
1040
  delete: {
1039
1041
  hotkeys: {
@@ -1404,6 +1406,9 @@ interface Config {
1404
1406
  iframeDoctype: string;
1405
1407
  /**
1406
1408
  * Custom style to be used inside the iframe to display content.
1409
+ * It goes in after `iframeCSSLinks`. The rules Jodit's own plugins need
1410
+ * in the editing document are added apart from it, so a value of your
1411
+ * own doesn't have to repeat them.
1407
1412
  *
1408
1413
  * ```javascript
1409
1414
  * Jodit.make('#editor', {
@@ -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.4";
6
+ export const APP_VERSION = "4.18.7";
7
7
  // prettier-ignore
8
8
  export const ES = "es2020";
9
9
  export const IS_ES_MODERN = true;
@@ -6,6 +6,8 @@
6
6
  import { IS_PROD } from "../../constants.js";
7
7
  import { getClassName } from "../../helpers/utils/get-class-name.js";
8
8
  const componentRegistry = new Map();
9
+ const decoratedClasses = new WeakSet();
10
+ const warnedClasses = new WeakSet();
9
11
  /**
10
12
  * Decorate components and set status isReady after constructor
11
13
  * @param constructorFunction - Component constructor class
@@ -24,8 +26,18 @@ export function component(constructorFunction) {
24
26
  }
25
27
  this.setStatus('ready');
26
28
  }
29
+ else if (!IS_PROD &&
30
+ !decoratedClasses.has(this.constructor) &&
31
+ !warnedClasses.has(this.constructor)) {
32
+ // Only the outermost class's decorator sets `ready`, so a subclass
33
+ // without its own decorator stays in `beforeInit`. See #1685
34
+ warnedClasses.add(this.constructor);
35
+ console.warn(`Component "${this.constructor.name || getClassName(this)}" extends a @component class without being decorated itself, so it never becomes ready and its @watch, @debounce, @throttle and @hook handlers never run`);
36
+ }
27
37
  }
28
38
  }
39
+ decoratedClasses.add(constructorFunction);
40
+ decoratedClasses.add(newConstructorFunction);
29
41
  const name = getClassName(constructorFunction.prototype);
30
42
  if (componentRegistry.has(name) && !IS_PROD) {
31
43
  throw new Error(`Component with name "${name}" is already registered`);
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { globalDocument, IS_PROD } from "../../constants.js";
10
10
  import { Dom } from "../../dom/dom.js";
11
+ import { readWordListItems, wrapWordListItems } from "./word-lists.js";
11
12
  import { trim } from "../string/trim.js";
12
13
  import { attr } from "../utils/attr.js";
13
14
  import { css } from "../utils/css.js";
@@ -60,6 +61,7 @@ export function applyStyles(html) {
60
61
  iframeDoc.open();
61
62
  iframeDoc.write(html);
62
63
  iframeDoc.close();
64
+ const listItems = readWordListItems(iframeDoc.body);
63
65
  // Word marks its auto-generated list markers (the literal
64
66
  // bullet/number, e.g. `1.` or `·`) with `mso-list:Ignore`.
65
67
  // They are display-only and must not be imported, otherwise
@@ -95,6 +97,7 @@ export function applyStyles(html) {
95
97
  throw e;
96
98
  }
97
99
  }
100
+ wrapWordListItems(listItems);
98
101
  Dom.each(iframeDoc.body, node => {
99
102
  if (Dom.isElement(node)) {
100
103
  const cssText = attr(node, 'style');
@@ -9,6 +9,7 @@
9
9
  import { globalDocument } from "../../constants.js";
10
10
  import { Dom } from "../../dom/dom.js";
11
11
  import { toArray } from "../array/to-array.js";
12
+ import { readWordListItems, wrapWordListItems } from "./word-lists.js";
12
13
  import { trim } from "../string/trim.js";
13
14
  import { attr, attrRaw } from "../utils/attr.js";
14
15
  /**
@@ -25,6 +26,7 @@ export function cleanFromWord(html) {
25
26
  const div = globalDocument.createElement('div');
26
27
  div.innerHTML = html;
27
28
  const marks = [];
29
+ const listItems = readWordListItems(div);
28
30
  if (div.firstChild) {
29
31
  Dom.each(div, node => {
30
32
  if (!node) {
@@ -76,6 +78,7 @@ export function cleanFromWord(html) {
76
78
  });
77
79
  }
78
80
  Dom.safeRemove.apply(null, marks);
81
+ wrapWordListItems(listItems);
79
82
  convertedString = div.innerHTML;
80
83
  }
81
84
  catch (e) { }
@@ -16,8 +16,12 @@ const NEW_LINE_TAGS = new Set([
16
16
  'h4',
17
17
  'h5',
18
18
  'h6',
19
- 'hr'
19
+ 'hr',
20
+ 'ul',
21
+ 'ol',
22
+ 'li'
20
23
  ]);
24
+ const LIST_TAGS = new Set(['ul', 'ol']);
21
25
  const INVISIBLE_TAGS = new Set(['script', 'style']);
22
26
  const ALONE_TAGS = new Set(['br', 'hr', 'input']);
23
27
  /**
@@ -37,6 +41,16 @@ export function stripTags(html, doc = document, exclude = null, blockBr = false)
37
41
  Dom.each(tmp, node => {
38
42
  Dom.isElement(node) && all.push(node);
39
43
  });
44
+ // By default blocks are joined with a single space (single-line
45
+ // plain text). When `blockBr` is set, separate them with a line
46
+ // break instead, so paragraph structure survives — e.g. the
47
+ // "Insert only Text" paste option. See #1232
48
+ const separators = new Set();
49
+ const separator = () => {
50
+ const text = doc.createTextNode(blockBr ? '%%%jodit-single-br%%%' : ' ');
51
+ separators.add(text);
52
+ return text;
53
+ };
40
54
  all.forEach(p => {
41
55
  const pr = p.parentNode;
42
56
  if (!pr) {
@@ -58,16 +72,20 @@ export function stripTags(html, doc = document, exclude = null, blockBr = false)
58
72
  if (!Dom.isTag(p, NEW_LINE_TAGS)) {
59
73
  return;
60
74
  }
75
+ const pv = p.previousSibling;
76
+ // A nested list starts on a new line after its item's own text
77
+ if (Dom.isTag(p, LIST_TAGS) &&
78
+ pv &&
79
+ !separators.has(pv) &&
80
+ !(Dom.isText(pv) && /\s$/.test(pv.nodeValue || ''))) {
81
+ Dom.before(p, separator());
82
+ }
61
83
  const nx = p.nextSibling;
62
84
  if (Dom.isText(nx) && /^\s/.test(nx.nodeValue || '')) {
63
85
  return;
64
86
  }
65
87
  if (nx) {
66
- // By default blocks are joined with a single space (single-line
67
- // plain text). When `blockBr` is set, separate them with a line
68
- // break instead, so paragraph structure survives — e.g. the
69
- // "Insert only Text" paste option. See #1232
70
- Dom.before(nx, doc.createTextNode(blockBr ? '%%%jodit-single-br%%%' : ' '));
88
+ Dom.before(nx, separator());
71
89
  }
72
90
  });
73
91
  return restoreTags(trim(tmp.innerText));
@@ -0,0 +1,29 @@
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
+ * A Word list paragraph, read before its `mso-list` style is lost
8
+ */
9
+ export interface WordListItem {
10
+ paragraph: HTMLElement;
11
+ list: string;
12
+ level: number;
13
+ tag: 'ul' | 'ol';
14
+ listStyleType: string | null;
15
+ start: number;
16
+ }
17
+ /**
18
+ * Finds Word's list paragraphs (`<p style="mso-list:l0 level1 lfo1">`), reads
19
+ * their list, level and type from the `mso-list:Ignore` marker, and removes the
20
+ * marker. Call it before anything strips the `mso-list` styles, then pass the
21
+ * result to {@link wrapWordListItems}
22
+ */
23
+ export declare function readWordListItems(root: HTMLElement): WordListItem[];
24
+ /**
25
+ * Turns the paragraphs found by {@link readWordListItems} into `<ul>`/`<ol>`
26
+ * lists, nesting them by level. Each paragraph becomes an `<li>` without Word's
27
+ * hanging indent
28
+ */
29
+ export declare function wrapWordListItems(items: WordListItem[]): void;
@@ -0,0 +1,158 @@
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/html
8
+ */
9
+ import { Dom } from "../../dom/dom.js";
10
+ import { toArray } from "../array/to-array.js";
11
+ import { attr } from "../utils/attr.js";
12
+ const ROMAN = {
13
+ i: 1,
14
+ v: 5,
15
+ x: 10,
16
+ l: 50,
17
+ c: 100,
18
+ d: 500,
19
+ m: 1000
20
+ };
21
+ function romanValue(roman) {
22
+ let value = 0;
23
+ for (let i = 0; i < roman.length; i += 1) {
24
+ const current = ROMAN[roman[i]], next = ROMAN[roman[i + 1]] || 0;
25
+ value += current < next ? -current : current;
26
+ }
27
+ return value;
28
+ }
29
+ /**
30
+ * Word's marker text (`1.`, `b)`, `iv.`, `·`, `o`, `§`) decides the list type
31
+ */
32
+ function parseMarker(marker) {
33
+ const text = marker.replace(/\s+/g, '');
34
+ const decimal = /^\(?(?:\d+\.)*(\d+)[.)]?$/.exec(text);
35
+ if (decimal) {
36
+ return { tag: 'ol', listStyleType: null, start: Number(decimal[1]) };
37
+ }
38
+ const letters = /^\(?([a-z]+|[A-Z]+)[.)]$/.exec(text);
39
+ if (letters) {
40
+ const value = letters[1], lower = value.toLowerCase(), upper = value !== lower;
41
+ if (/^[ivxlcdm]+$/.test(lower) && (lower.length > 1 || lower === 'i')) {
42
+ return {
43
+ tag: 'ol',
44
+ listStyleType: upper ? 'upper-roman' : 'lower-roman',
45
+ start: romanValue(lower)
46
+ };
47
+ }
48
+ // Word continues a lettered list with aa, bb, ...
49
+ if (/^([a-z])\1*$/.test(lower)) {
50
+ return {
51
+ tag: 'ol',
52
+ listStyleType: upper ? 'upper-alpha' : 'lower-alpha',
53
+ start: (lower.length - 1) * 26 + lower.charCodeAt(0) - 96
54
+ };
55
+ }
56
+ }
57
+ return { tag: 'ul', listStyleType: null, start: 1 };
58
+ }
59
+ /**
60
+ * Removes the marker and the wrappers it leaves empty
61
+ */
62
+ function removeMarker(marker, paragraph) {
63
+ let parent = marker.parentNode;
64
+ Dom.safeRemove(marker);
65
+ while (parent &&
66
+ parent !== paragraph &&
67
+ Dom.isElement(parent) &&
68
+ Dom.isEmpty(parent)) {
69
+ const next = parent.parentNode;
70
+ Dom.safeRemove(parent);
71
+ parent = next;
72
+ }
73
+ }
74
+ /**
75
+ * Finds Word's list paragraphs (`<p style="mso-list:l0 level1 lfo1">`), reads
76
+ * their list, level and type from the `mso-list:Ignore` marker, and removes the
77
+ * marker. Call it before anything strips the `mso-list` styles, then pass the
78
+ * result to {@link wrapWordListItems}
79
+ */
80
+ export function readWordListItems(root) {
81
+ const items = [];
82
+ toArray(root.querySelectorAll('p')).forEach(paragraph => {
83
+ const match = /mso-list:\s*l(\d+)\s+level(\d+)(?:\s+lfo(\d+))?/i.exec(attr(paragraph, 'style') || '');
84
+ if (!match) {
85
+ return;
86
+ }
87
+ const marker = toArray(paragraph.querySelectorAll('[style]')).find(elm => /mso-list:\s*ignore/i.test(attr(elm, 'style') || ''));
88
+ items.push({
89
+ paragraph,
90
+ list: `${match[1]}:${match[3] || ''}`,
91
+ level: Number(match[2]),
92
+ ...parseMarker((marker === null || marker === void 0 ? void 0 : marker.textContent) || '')
93
+ });
94
+ if (marker) {
95
+ removeMarker(marker, paragraph);
96
+ }
97
+ });
98
+ return items;
99
+ }
100
+ function isAdjacent(prev, item) {
101
+ return (Dom.findSibling(item.paragraph, true, n => !Dom.isEmptyTextNode(n) && !Dom.isComment(n)) === prev.paragraph);
102
+ }
103
+ /**
104
+ * Turns the paragraphs found by {@link readWordListItems} into `<ul>`/`<ol>`
105
+ * lists, nesting them by level. Each paragraph becomes an `<li>` without Word's
106
+ * hanging indent
107
+ */
108
+ export function wrapWordListItems(items) {
109
+ let stack = [];
110
+ const continues = items.map((item, index) => index > 0 && isAdjacent(items[index - 1], item));
111
+ items.forEach((item, index) => {
112
+ const { paragraph } = item;
113
+ if (!continues[index]) {
114
+ stack = [];
115
+ }
116
+ while (stack.length && stack[stack.length - 1].level > item.level) {
117
+ stack.pop();
118
+ }
119
+ let top = stack[stack.length - 1];
120
+ if (top && top.level === item.level && top.list !== item.list) {
121
+ stack.pop();
122
+ top = stack[stack.length - 1];
123
+ }
124
+ const doc = paragraph.ownerDocument;
125
+ if (!top || top.level < item.level) {
126
+ const element = doc.createElement(item.tag);
127
+ if (item.listStyleType) {
128
+ element.style.listStyleType = item.listStyleType;
129
+ }
130
+ if (item.tag === 'ol' && item.start !== 1) {
131
+ attr(element, 'start', item.start);
132
+ }
133
+ if (top && top.li) {
134
+ top.li.appendChild(element);
135
+ }
136
+ else {
137
+ Dom.before(paragraph, element);
138
+ }
139
+ top = { level: item.level, list: item.list, element };
140
+ stack.push(top);
141
+ }
142
+ const li = doc.createElement('li');
143
+ toArray(paragraph.attributes).forEach(attribute => {
144
+ if (attribute.name !== 'class') {
145
+ li.setAttribute(attribute.name, attribute.value);
146
+ }
147
+ });
148
+ li.style.removeProperty('margin-left');
149
+ li.style.removeProperty('text-indent');
150
+ if (!attr(li, 'style')) {
151
+ attr(li, 'style', null);
152
+ }
153
+ Dom.moveContent(paragraph, li);
154
+ Dom.safeRemove(paragraph);
155
+ top.element.appendChild(li);
156
+ top.li = li;
157
+ });
158
+ }
@@ -4,6 +4,7 @@
4
4
  * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
5
  */
6
6
  export * from "./composed-ranges";
7
+ export * from "./is-moved";
7
8
  export * from "./move-node-inside-start";
8
9
  export * from "./move-the-node-along-the-edge-outward";
9
10
  /**
@@ -9,6 +9,7 @@
9
9
  import { INVISIBLE_SPACE_REG_EXP_END as INV_END, INVISIBLE_SPACE_REG_EXP_START as INV_START } from "../../constants.js";
10
10
  import { Dom } from "../../dom/dom.js";
11
11
  export * from "./composed-ranges.js";
12
+ export * from "./is-moved.js";
12
13
  export * from "./move-node-inside-start.js";
13
14
  export * from "./move-the-node-along-the-edge-outward.js";
14
15
  /**
@@ -0,0 +1,15 @@
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 selection
8
+ */
9
+ import type { IJodit, Nullable } from "../../../types/index";
10
+ /**
11
+ * Whether the selection, `before` as it was when a key went down, has since
12
+ * moved to somewhere else in the editor
13
+ * @private
14
+ */
15
+ export declare function isMoved(jodit: IJodit, before: Nullable<Range>): boolean;
@@ -0,0 +1,22 @@
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
+ * Whether the selection, `before` as it was when a key went down, has since
8
+ * moved to somewhere else in the editor
9
+ * @private
10
+ */
11
+ export function isMoved(jodit, before) {
12
+ const { s } = jodit;
13
+ // Selecting table cells, for one, removes the selection altogether
14
+ if (!before || !s.isInsideArea) {
15
+ return false;
16
+ }
17
+ const { range } = s;
18
+ return (range.startContainer !== before.startContainer ||
19
+ range.startOffset !== before.startOffset ||
20
+ range.endContainer !== before.endContainer ||
21
+ range.endOffset !== before.endOffset);
22
+ }
@@ -4,8 +4,10 @@
4
4
  * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
5
  */
6
6
  import { Dom } from "../../../../dom/dom.js";
7
+ import { size } from "../../../../helpers/size/object-size.js";
7
8
  import { assert } from "../../../../helpers/utils/assert.js";
8
9
  import { extractSelectedPart } from "../extract.js";
10
+ import { elementsEqualAttributes } from "../is-same-attributes.js";
9
11
  import { toggleAttributes } from "../toggle-attributes.js";
10
12
  import { _PREFIX, CHANGE, INITIAL, REPLACE, UNWRAP } from "../../constants.js";
11
13
  import { wrapList } from "./wrap-list.js";
@@ -28,10 +30,23 @@ export function toggleOrderedList(commitStyle, li, jodit, mode) {
28
30
  const hook = jodit.e.fire.bind(jodit.e, `${_PREFIX}AfterToggleList`);
29
31
  if (mode !== UNWRAP) {
30
32
  const isChangeMode = toggleAttributes(commitStyle, li.parentElement, jodit, INITIAL, true) === CHANGE;
33
+ const isSameList = Dom.isTag(list, commitStyle.element);
34
+ // ol => ol[style="list-style-type: lower-roman"]: a list's style
35
+ // belongs to the whole list, so it is changed without splitting it
36
+ if (isSameList &&
37
+ isChangeMode &&
38
+ mode !== REPLACE &&
39
+ changesStyleOnly(commitStyle)) {
40
+ toggleAttributes(commitStyle, list, jodit, CHANGE);
41
+ hook(CHANGE, joinPrevious(commitStyle, list, mode), commitStyle);
42
+ return CHANGE;
43
+ }
44
+ // Restyled already, for an earlier part of the selection
45
+ if (isSameList && mode === CHANGE) {
46
+ return CHANGE;
47
+ }
31
48
  // ul => ol, ol => ul or ul => ul.class1
32
- if (mode === REPLACE ||
33
- isChangeMode ||
34
- list.tagName.toLowerCase() !== commitStyle.element) {
49
+ if (mode === REPLACE || isChangeMode || !isSameList) {
35
50
  const wrapper = unwrapList(REPLACE, list, li, jodit, commitStyle);
36
51
  const newList = wrapList(commitStyle, wrapper, jodit);
37
52
  hook(REPLACE, newList, commitStyle);
@@ -42,6 +57,28 @@ export function toggleOrderedList(commitStyle, li, jodit, mode) {
42
57
  hook(UNWRAP, wrapper, commitStyle);
43
58
  return UNWRAP;
44
59
  }
60
+ /**
61
+ * Other attributes, such as a class, still move the items into a new list,
62
+ * where the `beforeWrapList` hook can add markup to each of them
63
+ */
64
+ function changesStyleOnly({ options: { attributes } }) {
65
+ return Boolean(attributes === null || attributes === void 0 ? void 0 : attributes.style) && size(attributes) === 1;
66
+ }
67
+ /**
68
+ * Joins a restyled list to the list before it, when the selection runs on
69
+ * from that list and the two now match
70
+ */
71
+ function joinPrevious(commitStyle, list, mode) {
72
+ const prev = Dom.findSibling(list, true);
73
+ if (mode !== CHANGE ||
74
+ !Dom.isTag(prev, commitStyle.element) ||
75
+ !elementsEqualAttributes(prev, list)) {
76
+ return list;
77
+ }
78
+ Dom.append(prev, Array.from(list.childNodes));
79
+ Dom.safeRemove(list);
80
+ return prev;
81
+ }
45
82
  function unwrapList(mode, list, li, jodit, cs) {
46
83
  const result = jodit.e.fire(`${_PREFIX}BeforeUnwrapList`, mode, list, cs);
47
84
  if (result) {
@@ -7,7 +7,7 @@ import { LIST_TAGS } from "../../constants.js";
7
7
  import { Dom } from "../../dom/dom.js";
8
8
  import { assert } from "../../helpers/utils/assert.js";
9
9
  import { extractSelectedPart, getSuitChild, getSuitParent, isInsideInvisibleElement, suitableClosest, toggleAttributes, toggleOrderedList, unwrapChildren, wrap, WRAP_NODES } from "./api/index.js";
10
- import { INITIAL, REPLACE, UNSET, UNWRAP, WRAP } from "./constants.js";
10
+ import { CHANGE, INITIAL, REPLACE, UNSET, UNWRAP, WRAP } from "./constants.js";
11
11
  export const states = {
12
12
  START: 'START',
13
13
  ELEMENT: 'ELEMENT',
@@ -54,7 +54,10 @@ export const transactions = {
54
54
  [states.LIST]: {
55
55
  exec(value) {
56
56
  const { element, jodit, mode } = value;
57
- if (mode !== INITIAL && mode !== UNWRAP && mode !== REPLACE) {
57
+ if (mode !== INITIAL &&
58
+ mode !== UNWRAP &&
59
+ mode !== REPLACE &&
60
+ mode !== CHANGE) {
58
61
  return { ...value, next: states.END };
59
62
  }
60
63
  const li = Dom.closest(element, 'li', jodit.editor);