@ckeditor/ckeditor5-list 47.6.1 → 48.0.0-alpha.1

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 (275) hide show
  1. package/LICENSE.md +1 -1
  2. package/ckeditor5-metadata.json +21 -18
  3. package/dist/index-content.css +135 -111
  4. package/dist/index-editor.css +152 -73
  5. package/dist/index.css +204 -238
  6. package/dist/index.css.map +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/{src → dist}/legacylist/legacyconverters.d.ts +2 -2
  9. package/{src → dist}/legacylist/legacyindentcommand.d.ts +1 -1
  10. package/{src → dist}/legacylist/legacylistcommand.d.ts +1 -1
  11. package/{src → dist}/legacylist/legacylistediting.d.ts +3 -3
  12. package/{src → dist}/legacylist/legacylistutils.d.ts +2 -2
  13. package/{src → dist}/legacylist/legacyutils.d.ts +1 -1
  14. package/{src → dist}/legacylist.d.ts +1 -1
  15. package/{src → dist}/legacylistproperties/legacylistpropertiesediting.d.ts +1 -1
  16. package/{src → dist}/legacylistproperties/legacylistreversedcommand.d.ts +1 -1
  17. package/{src → dist}/legacylistproperties/legacyliststartcommand.d.ts +1 -1
  18. package/{src → dist}/legacylistproperties/legacyliststylecommand.d.ts +1 -1
  19. package/{src → dist}/legacylistproperties.d.ts +1 -1
  20. package/{src → dist}/legacytodolist/legacychecktodolistcommand.d.ts +2 -2
  21. package/{src → dist}/legacytodolist/legacytodolistconverters.d.ts +2 -2
  22. package/{src → dist}/legacytodolist/legacytodolistediting.d.ts +1 -1
  23. package/{src → dist}/legacytodolist.d.ts +1 -1
  24. package/{src → dist}/list/adjacentlistssupport.d.ts +1 -1
  25. package/{src → dist}/list/converters.d.ts +2 -2
  26. package/{src → dist}/list/listcommand.d.ts +2 -2
  27. package/{src → dist}/list/listediting.d.ts +5 -5
  28. package/{src → dist}/list/listindentcommand.d.ts +2 -2
  29. package/{src → dist}/list/listmergecommand.d.ts +2 -2
  30. package/{src → dist}/list/listsplitcommand.d.ts +2 -2
  31. package/{src → dist}/list/listui.d.ts +1 -1
  32. package/{src → dist}/list/listutils.d.ts +3 -3
  33. package/{src → dist}/list/utils/listwalker.d.ts +2 -2
  34. package/{src → dist}/list/utils/model.d.ts +2 -2
  35. package/{src → dist}/list/utils/postfixers.d.ts +1 -1
  36. package/{src → dist}/list/utils/view.d.ts +1 -1
  37. package/{src → dist}/list/utils.d.ts +1 -1
  38. package/{src → dist}/list.d.ts +1 -1
  39. package/{src → dist}/listconfig.d.ts +1 -1
  40. package/{src → dist}/listformatting/listitemboldintegration.d.ts +1 -1
  41. package/{src → dist}/listformatting/listitemfontcolorintegration.d.ts +1 -1
  42. package/{src → dist}/listformatting/listitemfontfamilyintegration.d.ts +1 -1
  43. package/{src → dist}/listformatting/listitemfontsizeintegration.d.ts +1 -1
  44. package/{src → dist}/listformatting/listitemitalicintegration.d.ts +1 -1
  45. package/{src → dist}/listformatting.d.ts +1 -1
  46. package/{src → dist}/listproperties/converters.d.ts +2 -2
  47. package/{src → dist}/listproperties/listpropertiesediting.d.ts +2 -2
  48. package/{src → dist}/listproperties/listpropertiesui.d.ts +1 -1
  49. package/{src → dist}/listproperties/listpropertiesutils.d.ts +1 -1
  50. package/{src → dist}/listproperties/listreversedcommand.d.ts +1 -1
  51. package/{src → dist}/listproperties/liststartcommand.d.ts +1 -1
  52. package/{src → dist}/listproperties/liststylecommand.d.ts +1 -1
  53. package/{src → dist}/listproperties/ui/listpropertiesview.d.ts +2 -2
  54. package/{src → dist}/listproperties.d.ts +1 -1
  55. package/{src → dist}/todolist/checktodolistcommand.d.ts +1 -1
  56. package/{src → dist}/todolist/todocheckboxchangeobserver.d.ts +1 -1
  57. package/{src → dist}/todolist/todolistediting.d.ts +1 -1
  58. package/{src → dist}/todolist/todolistui.d.ts +1 -1
  59. package/{src → dist}/todolist.d.ts +1 -1
  60. package/package.json +28 -52
  61. package/build/list.js +0 -5
  62. package/build/translations/af.js +0 -1
  63. package/build/translations/ar.js +0 -1
  64. package/build/translations/ast.js +0 -1
  65. package/build/translations/az.js +0 -1
  66. package/build/translations/be.js +0 -1
  67. package/build/translations/bg.js +0 -1
  68. package/build/translations/bn.js +0 -1
  69. package/build/translations/bs.js +0 -1
  70. package/build/translations/ca.js +0 -1
  71. package/build/translations/cs.js +0 -1
  72. package/build/translations/da.js +0 -1
  73. package/build/translations/de-ch.js +0 -1
  74. package/build/translations/de.js +0 -1
  75. package/build/translations/el.js +0 -1
  76. package/build/translations/en-au.js +0 -1
  77. package/build/translations/en-gb.js +0 -1
  78. package/build/translations/eo.js +0 -1
  79. package/build/translations/es-co.js +0 -1
  80. package/build/translations/es.js +0 -1
  81. package/build/translations/et.js +0 -1
  82. package/build/translations/eu.js +0 -1
  83. package/build/translations/fa.js +0 -1
  84. package/build/translations/fi.js +0 -1
  85. package/build/translations/fr.js +0 -1
  86. package/build/translations/gl.js +0 -1
  87. package/build/translations/gu.js +0 -1
  88. package/build/translations/he.js +0 -1
  89. package/build/translations/hi.js +0 -1
  90. package/build/translations/hr.js +0 -1
  91. package/build/translations/hu.js +0 -1
  92. package/build/translations/hy.js +0 -1
  93. package/build/translations/id.js +0 -1
  94. package/build/translations/it.js +0 -1
  95. package/build/translations/ja.js +0 -1
  96. package/build/translations/jv.js +0 -1
  97. package/build/translations/kk.js +0 -1
  98. package/build/translations/km.js +0 -1
  99. package/build/translations/kn.js +0 -1
  100. package/build/translations/ko.js +0 -1
  101. package/build/translations/ku.js +0 -1
  102. package/build/translations/lt.js +0 -1
  103. package/build/translations/lv.js +0 -1
  104. package/build/translations/ms.js +0 -1
  105. package/build/translations/nb.js +0 -1
  106. package/build/translations/ne.js +0 -1
  107. package/build/translations/nl.js +0 -1
  108. package/build/translations/no.js +0 -1
  109. package/build/translations/oc.js +0 -1
  110. package/build/translations/pl.js +0 -1
  111. package/build/translations/pt-br.js +0 -1
  112. package/build/translations/pt.js +0 -1
  113. package/build/translations/ro.js +0 -1
  114. package/build/translations/ru.js +0 -1
  115. package/build/translations/si.js +0 -1
  116. package/build/translations/sk.js +0 -1
  117. package/build/translations/sl.js +0 -1
  118. package/build/translations/sq.js +0 -1
  119. package/build/translations/sr-latn.js +0 -1
  120. package/build/translations/sr.js +0 -1
  121. package/build/translations/sv.js +0 -1
  122. package/build/translations/th.js +0 -1
  123. package/build/translations/ti.js +0 -1
  124. package/build/translations/tk.js +0 -1
  125. package/build/translations/tr.js +0 -1
  126. package/build/translations/tt.js +0 -1
  127. package/build/translations/ug.js +0 -1
  128. package/build/translations/uk.js +0 -1
  129. package/build/translations/ur.js +0 -1
  130. package/build/translations/uz.js +0 -1
  131. package/build/translations/vi.js +0 -1
  132. package/build/translations/zh-cn.js +0 -1
  133. package/build/translations/zh.js +0 -1
  134. package/lang/contexts.json +0 -37
  135. package/lang/translations/af.po +0 -152
  136. package/lang/translations/ar.po +0 -152
  137. package/lang/translations/ast.po +0 -152
  138. package/lang/translations/az.po +0 -152
  139. package/lang/translations/be.po +0 -152
  140. package/lang/translations/bg.po +0 -152
  141. package/lang/translations/bn.po +0 -152
  142. package/lang/translations/bs.po +0 -152
  143. package/lang/translations/ca.po +0 -152
  144. package/lang/translations/cs.po +0 -152
  145. package/lang/translations/da.po +0 -152
  146. package/lang/translations/de-ch.po +0 -152
  147. package/lang/translations/de.po +0 -152
  148. package/lang/translations/el.po +0 -152
  149. package/lang/translations/en-au.po +0 -152
  150. package/lang/translations/en-gb.po +0 -152
  151. package/lang/translations/en.po +0 -152
  152. package/lang/translations/eo.po +0 -152
  153. package/lang/translations/es-co.po +0 -152
  154. package/lang/translations/es.po +0 -152
  155. package/lang/translations/et.po +0 -152
  156. package/lang/translations/eu.po +0 -152
  157. package/lang/translations/fa.po +0 -152
  158. package/lang/translations/fi.po +0 -152
  159. package/lang/translations/fr.po +0 -152
  160. package/lang/translations/gl.po +0 -152
  161. package/lang/translations/gu.po +0 -152
  162. package/lang/translations/he.po +0 -152
  163. package/lang/translations/hi.po +0 -152
  164. package/lang/translations/hr.po +0 -152
  165. package/lang/translations/hu.po +0 -152
  166. package/lang/translations/hy.po +0 -152
  167. package/lang/translations/id.po +0 -152
  168. package/lang/translations/it.po +0 -152
  169. package/lang/translations/ja.po +0 -152
  170. package/lang/translations/jv.po +0 -152
  171. package/lang/translations/kk.po +0 -152
  172. package/lang/translations/km.po +0 -152
  173. package/lang/translations/kn.po +0 -152
  174. package/lang/translations/ko.po +0 -152
  175. package/lang/translations/ku.po +0 -152
  176. package/lang/translations/lt.po +0 -152
  177. package/lang/translations/lv.po +0 -152
  178. package/lang/translations/ms.po +0 -152
  179. package/lang/translations/nb.po +0 -152
  180. package/lang/translations/ne.po +0 -152
  181. package/lang/translations/nl.po +0 -152
  182. package/lang/translations/no.po +0 -152
  183. package/lang/translations/oc.po +0 -152
  184. package/lang/translations/pl.po +0 -152
  185. package/lang/translations/pt-br.po +0 -152
  186. package/lang/translations/pt.po +0 -152
  187. package/lang/translations/ro.po +0 -152
  188. package/lang/translations/ru.po +0 -152
  189. package/lang/translations/si.po +0 -152
  190. package/lang/translations/sk.po +0 -152
  191. package/lang/translations/sl.po +0 -152
  192. package/lang/translations/sq.po +0 -152
  193. package/lang/translations/sr-latn.po +0 -152
  194. package/lang/translations/sr.po +0 -152
  195. package/lang/translations/sv.po +0 -152
  196. package/lang/translations/th.po +0 -152
  197. package/lang/translations/ti.po +0 -152
  198. package/lang/translations/tk.po +0 -152
  199. package/lang/translations/tr.po +0 -152
  200. package/lang/translations/tt.po +0 -152
  201. package/lang/translations/ug.po +0 -152
  202. package/lang/translations/uk.po +0 -152
  203. package/lang/translations/ur.po +0 -152
  204. package/lang/translations/uz.po +0 -152
  205. package/lang/translations/vi.po +0 -152
  206. package/lang/translations/zh-cn.po +0 -152
  207. package/lang/translations/zh.po +0 -152
  208. package/src/augmentation.js +0 -5
  209. package/src/index.js +0 -67
  210. package/src/legacyerrors.js +0 -28
  211. package/src/legacylist/legacyconverters.js +0 -921
  212. package/src/legacylist/legacyindentcommand.js +0 -111
  213. package/src/legacylist/legacylistcommand.js +0 -278
  214. package/src/legacylist/legacylistediting.js +0 -167
  215. package/src/legacylist/legacylistutils.js +0 -52
  216. package/src/legacylist/legacyutils.js +0 -357
  217. package/src/legacylist.js +0 -36
  218. package/src/legacylistproperties/legacylistpropertiesediting.js +0 -703
  219. package/src/legacylistproperties/legacylistreversedcommand.js +0 -52
  220. package/src/legacylistproperties/legacyliststartcommand.js +0 -52
  221. package/src/legacylistproperties/legacyliststylecommand.js +0 -105
  222. package/src/legacylistproperties.js +0 -37
  223. package/src/legacytodolist/legacychecktodolistcommand.js +0 -82
  224. package/src/legacytodolist/legacytodolistconverters.js +0 -268
  225. package/src/legacytodolist/legacytodolistediting.js +0 -199
  226. package/src/legacytodolist.js +0 -37
  227. package/src/list/adjacentlistssupport.js +0 -87
  228. package/src/list/converters.js +0 -533
  229. package/src/list/listcommand.js +0 -176
  230. package/src/list/listediting.js +0 -696
  231. package/src/list/listindentcommand.js +0 -136
  232. package/src/list/listmergecommand.js +0 -182
  233. package/src/list/listsplitcommand.js +0 -74
  234. package/src/list/listui.js +0 -42
  235. package/src/list/listutils.js +0 -68
  236. package/src/list/utils/listwalker.js +0 -236
  237. package/src/list/utils/model.js +0 -487
  238. package/src/list/utils/postfixers.js +0 -131
  239. package/src/list/utils/view.js +0 -117
  240. package/src/list/utils.js +0 -51
  241. package/src/list.js +0 -36
  242. package/src/listconfig.js +0 -5
  243. package/src/listformatting/listitemboldintegration.js +0 -88
  244. package/src/listformatting/listitemfontcolorintegration.js +0 -92
  245. package/src/listformatting/listitemfontfamilyintegration.js +0 -93
  246. package/src/listformatting/listitemfontsizeintegration.js +0 -124
  247. package/src/listformatting/listitemitalicintegration.js +0 -88
  248. package/src/listformatting.js +0 -248
  249. package/src/listproperties/converters.js +0 -43
  250. package/src/listproperties/listpropertiesediting.js +0 -291
  251. package/src/listproperties/listpropertiesui.js +0 -385
  252. package/src/listproperties/listpropertiesutils.js +0 -50
  253. package/src/listproperties/listreversedcommand.js +0 -55
  254. package/src/listproperties/liststartcommand.js +0 -61
  255. package/src/listproperties/liststylecommand.js +0 -121
  256. package/src/listproperties/ui/listpropertiesview.js +0 -318
  257. package/src/listproperties/utils/config.js +0 -84
  258. package/src/listproperties/utils/style.js +0 -85
  259. package/src/listproperties.js +0 -37
  260. package/src/todolist/checktodolistcommand.js +0 -82
  261. package/src/todolist/todocheckboxchangeobserver.js +0 -36
  262. package/src/todolist/todolistediting.js +0 -470
  263. package/src/todolist/todolistui.js +0 -35
  264. package/src/todolist.js +0 -37
  265. package/theme/documentlist.css +0 -8
  266. package/theme/list.css +0 -40
  267. package/theme/listformatting.css +0 -66
  268. package/theme/listproperties.css +0 -10
  269. package/theme/liststyles.css +0 -8
  270. package/theme/todolist.css +0 -140
  271. /package/{src → dist}/augmentation.d.ts +0 -0
  272. /package/{src → dist}/index.d.ts +0 -0
  273. /package/{src → dist}/legacyerrors.d.ts +0 -0
  274. /package/{src → dist}/listproperties/utils/config.d.ts +0 -0
  275. /package/{src → dist}/listproperties/utils/style.d.ts +0 -0
@@ -1,696 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
- */
5
- /**
6
- * @module list/list/listediting
7
- */
8
- import { Plugin } from 'ckeditor5/src/core.js';
9
- import { Delete } from 'ckeditor5/src/typing.js';
10
- import { Enter } from 'ckeditor5/src/enter.js';
11
- import { CKEditorError } from 'ckeditor5/src/utils.js';
12
- import { ListIndentCommand } from './listindentcommand.js';
13
- import { ListCommand } from './listcommand.js';
14
- import { ListMergeCommand } from './listmergecommand.js';
15
- import { ListSplitCommand } from './listsplitcommand.js';
16
- import { ListFormatting } from '../listformatting.js';
17
- import { ListUtils } from './listutils.js';
18
- import { bogusParagraphCreator, createModelToViewPositionMapper, listItemDowncastConverter, listItemDowncastRemoveConverter, listItemUpcastConverter, reconvertItemsOnDataChange } from './converters.js';
19
- import { findAndAddListHeadToMap, fixListIndents, fixListItemIds } from './utils/postfixers.js';
20
- import { getAllListItemBlocks, isFirstBlockOfListItem, isLastBlockOfListItem, isSingleListItem, getSelectedBlockObject, isListItemBlock, removeListAttributes, ListItemUid } from './utils/model.js';
21
- import { getViewElementIdForListType, getViewElementNameForListType } from './utils/view.js';
22
- import { ListWalker, ListBlocksIterable } from './utils/listwalker.js';
23
- import { ClipboardPipeline } from 'ckeditor5/src/clipboard.js';
24
- import '../../theme/documentlist.css';
25
- import '../../theme/list.css';
26
- /**
27
- * A list of base list model attributes.
28
- */
29
- const LIST_BASE_ATTRIBUTES = ['listType', 'listIndent', 'listItemId'];
30
- /**
31
- * The editing part of the document-list feature. It handles creating, editing and removing lists and list items.
32
- */
33
- export class ListEditing extends Plugin {
34
- /**
35
- * The list of registered downcast strategies.
36
- */
37
- _downcastStrategies = [];
38
- /**
39
- * @inheritDoc
40
- */
41
- static get pluginName() {
42
- return 'ListEditing';
43
- }
44
- /**
45
- * @inheritDoc
46
- */
47
- static get isOfficialPlugin() {
48
- return true;
49
- }
50
- /**
51
- * @inheritDoc
52
- */
53
- static get requires() {
54
- return [Enter, Delete, ListUtils, ClipboardPipeline, ListFormatting];
55
- }
56
- /**
57
- * @inheritDoc
58
- */
59
- constructor(editor) {
60
- super(editor);
61
- editor.config.define('list.multiBlock', true);
62
- }
63
- /**
64
- * @inheritDoc
65
- */
66
- init() {
67
- const editor = this.editor;
68
- const model = editor.model;
69
- const multiBlock = editor.config.get('list.multiBlock');
70
- if (editor.plugins.has('LegacyListEditing')) {
71
- /**
72
- * The `List` feature cannot be loaded together with the `LegacyList` plugin.
73
- *
74
- * @error list-feature-conflict
75
- * @param {string} conflictPlugin Name of the plugin.
76
- */
77
- throw new CKEditorError('list-feature-conflict', this, { conflictPlugin: 'LegacyListEditing' });
78
- }
79
- model.schema.register('$listItem', { allowAttributes: LIST_BASE_ATTRIBUTES });
80
- if (multiBlock) {
81
- model.schema.extend('$container', { allowAttributesOf: '$listItem' });
82
- model.schema.extend('$block', { allowAttributesOf: '$listItem' });
83
- model.schema.extend('$blockObject', { allowAttributesOf: '$listItem' });
84
- }
85
- else {
86
- model.schema.register('listItem', {
87
- inheritAllFrom: '$block',
88
- allowAttributesOf: '$listItem'
89
- });
90
- }
91
- for (const attribute of LIST_BASE_ATTRIBUTES) {
92
- model.schema.setAttributeProperties(attribute, {
93
- copyOnReplace: true
94
- });
95
- }
96
- // Register commands.
97
- editor.commands.add('numberedList', new ListCommand(editor, 'numbered'));
98
- editor.commands.add('bulletedList', new ListCommand(editor, 'bulleted'));
99
- editor.commands.add('customNumberedList', new ListCommand(editor, 'customNumbered', { multiLevel: true }));
100
- editor.commands.add('customBulletedList', new ListCommand(editor, 'customBulleted', { multiLevel: true }));
101
- editor.commands.add('indentList', new ListIndentCommand(editor, 'forward'));
102
- editor.commands.add('outdentList', new ListIndentCommand(editor, 'backward'));
103
- editor.commands.add('splitListItemBefore', new ListSplitCommand(editor, 'before'));
104
- editor.commands.add('splitListItemAfter', new ListSplitCommand(editor, 'after'));
105
- if (multiBlock) {
106
- editor.commands.add('mergeListItemBackward', new ListMergeCommand(editor, 'backward'));
107
- editor.commands.add('mergeListItemForward', new ListMergeCommand(editor, 'forward'));
108
- }
109
- this._setupDeleteIntegration();
110
- this._setupEnterIntegration();
111
- this._setupTabIntegration();
112
- this._setupClipboardIntegration();
113
- this._setupAccessibilityIntegration();
114
- this._setupListItemIdConversionStrategy();
115
- }
116
- /**
117
- * @inheritDoc
118
- */
119
- afterInit() {
120
- const editor = this.editor;
121
- const commands = editor.commands;
122
- const indent = commands.get('indent');
123
- const outdent = commands.get('outdent');
124
- if (indent) {
125
- // Priority is high due to integration with `IndentBlock` plugin. We want to indent list first and if it's not possible
126
- // user can indent content with `IndentBlock` plugin.
127
- indent.registerChildCommand(commands.get('indentList'), { priority: 'high' });
128
- }
129
- if (outdent) {
130
- // Priority is lowest due to integration with `IndentBlock` and `IndentCode` plugins.
131
- // First we want to allow user to outdent all indendations from other features then he can oudent list item.
132
- outdent.registerChildCommand(commands.get('outdentList'), { priority: 'lowest' });
133
- }
134
- // Register conversion and model post-fixer after other plugins had a chance to register their attribute strategies.
135
- this._setupModelPostFixing();
136
- this._setupConversion();
137
- }
138
- /**
139
- * Registers a downcast strategy.
140
- *
141
- * **Note**: Strategies must be registered in the `Plugin#init()` phase so that it can be applied
142
- * in the `ListEditing#afterInit()`.
143
- *
144
- * @param strategy The downcast strategy to register.
145
- */
146
- registerDowncastStrategy(strategy) {
147
- this._downcastStrategies.push(strategy);
148
- }
149
- /**
150
- * Returns list of model attribute names that should affect downcast conversion.
151
- */
152
- getListAttributeNames() {
153
- return [
154
- ...LIST_BASE_ATTRIBUTES,
155
- ...this._downcastStrategies.map(strategy => strategy.attributeName)
156
- ];
157
- }
158
- /**
159
- * Attaches the listener to the {@link module:engine/view/document~ViewDocument#event:delete} event and handles backspace/delete
160
- * keys in and around document lists.
161
- */
162
- _setupDeleteIntegration() {
163
- const editor = this.editor;
164
- const mergeBackwardCommand = editor.commands.get('mergeListItemBackward');
165
- const mergeForwardCommand = editor.commands.get('mergeListItemForward');
166
- this.listenTo(editor.editing.view.document, 'delete', (evt, data) => {
167
- const selection = editor.model.document.selection;
168
- // Let the Widget plugin take care of block widgets while deleting (https://github.com/ckeditor/ckeditor5/issues/11346).
169
- if (getSelectedBlockObject(editor.model)) {
170
- return;
171
- }
172
- editor.model.change(() => {
173
- const firstPosition = selection.getFirstPosition();
174
- if (selection.isCollapsed && data.direction == 'backward') {
175
- if (!firstPosition.isAtStart) {
176
- return;
177
- }
178
- const positionParent = firstPosition.parent;
179
- if (!isListItemBlock(positionParent)) {
180
- return;
181
- }
182
- const previousBlock = ListWalker.first(positionParent, {
183
- sameAttributes: 'listType',
184
- sameIndent: true
185
- });
186
- // Outdent the first block of a first list item.
187
- if (!previousBlock && positionParent.getAttribute('listIndent') === 0) {
188
- if (!isLastBlockOfListItem(positionParent)) {
189
- editor.execute('splitListItemAfter');
190
- }
191
- editor.execute('outdentList');
192
- }
193
- // Merge block with previous one (on the block level or on the content level).
194
- else {
195
- if (!mergeBackwardCommand || !mergeBackwardCommand.isEnabled) {
196
- return;
197
- }
198
- mergeBackwardCommand.execute({
199
- shouldMergeOnBlocksContentLevel: shouldMergeOnBlocksContentLevel(editor.model, 'backward')
200
- });
201
- }
202
- data.preventDefault();
203
- evt.stop();
204
- }
205
- // Non-collapsed selection or forward delete.
206
- else {
207
- // Collapsed selection should trigger forward merging only if at the end of a block.
208
- if (selection.isCollapsed && !selection.getLastPosition().isAtEnd) {
209
- return;
210
- }
211
- if (!mergeForwardCommand || !mergeForwardCommand.isEnabled) {
212
- return;
213
- }
214
- mergeForwardCommand.execute({
215
- shouldMergeOnBlocksContentLevel: shouldMergeOnBlocksContentLevel(editor.model, 'forward')
216
- });
217
- data.preventDefault();
218
- evt.stop();
219
- }
220
- });
221
- }, { context: 'li' });
222
- }
223
- /**
224
- * Attaches a listener to the {@link module:engine/view/document~ViewDocument#event:enter} event and handles enter key press
225
- * in document lists.
226
- */
227
- _setupEnterIntegration() {
228
- const editor = this.editor;
229
- const model = editor.model;
230
- const commands = editor.commands;
231
- const enterCommand = commands.get('enter');
232
- // Overwrite the default Enter key behavior: outdent or split the list in certain cases.
233
- this.listenTo(editor.editing.view.document, 'enter', (evt, data) => {
234
- const doc = model.document;
235
- const positionParent = doc.selection.getFirstPosition().parent;
236
- if (doc.selection.isCollapsed &&
237
- isListItemBlock(positionParent) &&
238
- positionParent.isEmpty &&
239
- !data.isSoft) {
240
- const isFirstBlock = isFirstBlockOfListItem(positionParent);
241
- const isLastBlock = isLastBlockOfListItem(positionParent);
242
- // * a → * a
243
- // * [] → []
244
- if (isFirstBlock && isLastBlock) {
245
- editor.execute('outdentList');
246
- data.preventDefault();
247
- evt.stop();
248
- }
249
- // * [] → * []
250
- // a → * a
251
- else if (isFirstBlock && !isLastBlock) {
252
- editor.execute('splitListItemAfter');
253
- data.preventDefault();
254
- evt.stop();
255
- }
256
- // * a → * a
257
- // [] → * []
258
- else if (isLastBlock) {
259
- editor.execute('splitListItemBefore');
260
- data.preventDefault();
261
- evt.stop();
262
- }
263
- }
264
- }, { context: 'li' });
265
- // In some cases, after the default block splitting, we want to modify the new block to become a new list item
266
- // instead of an additional block in the same list item.
267
- this.listenTo(enterCommand, 'afterExecute', () => {
268
- const splitCommand = commands.get('splitListItemBefore');
269
- // The command has not refreshed because the change block related to EnterCommand#execute() is not over yet.
270
- // Let's keep it up to date and take advantage of ListSplitCommand#isEnabled.
271
- splitCommand.refresh();
272
- if (!splitCommand.isEnabled) {
273
- return;
274
- }
275
- const doc = editor.model.document;
276
- const positionParent = doc.selection.getLastPosition().parent;
277
- const listItemBlocks = getAllListItemBlocks(positionParent);
278
- // Keep in mind this split happens after the default enter handler was executed. For instance:
279
- //
280
- // │ Initial state │ After default enter │ Here in #afterExecute │
281
- // ├───────────────────────────┼───────────────────────────┼───────────────────────────┤
282
- // │ * a[] │ * a │ * a │
283
- // │ │ [] │ * [] │
284
- if (listItemBlocks.length === 2) {
285
- splitCommand.execute();
286
- }
287
- });
288
- }
289
- /**
290
- * Attaches a listener to the {@link module:engine/view/document~ViewDocument#event:tab} event and handles tab key and tab+shift keys
291
- * presses in document lists.
292
- */
293
- _setupTabIntegration() {
294
- const editor = this.editor;
295
- this.listenTo(editor.editing.view.document, 'tab', (evt, data) => {
296
- const commandName = data.shiftKey ? 'outdentList' : 'indentList';
297
- const command = this.editor.commands.get(commandName);
298
- if (command.isEnabled) {
299
- editor.execute(commandName);
300
- data.stopPropagation();
301
- data.preventDefault();
302
- evt.stop();
303
- }
304
- }, { context: 'li' });
305
- }
306
- /**
307
- * Registers the conversion helpers for the document-list feature.
308
- */
309
- _setupConversion() {
310
- const editor = this.editor;
311
- const model = editor.model;
312
- const attributeNames = this.getListAttributeNames();
313
- const multiBlock = editor.config.get('list.multiBlock');
314
- const elementName = multiBlock ? 'paragraph' : 'listItem';
315
- editor.conversion.for('upcast')
316
- // Convert <li> to a generic paragraph (or listItem element) so the content of <li> is always inside a block.
317
- // Setting the listType attribute to let other features (to-do list) know that this is part of a list item.
318
- // This is also important to properly handle simple lists so that paragraphs inside a list item won't break the list item.
319
- // <li> <-- converted to listItem
320
- // <p></p> <-- should be also converted to listItem, so it won't split and replace the listItem generated from the above li.
321
- .elementToElement({
322
- view: 'li',
323
- model: (viewElement, { writer }) => writer.createElement(elementName, { listType: '' })
324
- })
325
- // Convert paragraph to the list block (without list type defined yet).
326
- // This is important to properly handle bogus paragraph and to-do lists.
327
- // Most of the time the bogus paragraph should not appear in the data of to-do list,
328
- // but if there is any marker or an attribute on the paragraph then the bogus paragraph
329
- // is preserved in the data, and we need to be able to detect this case.
330
- .elementToElement({
331
- view: 'p',
332
- model: (viewElement, { writer }) => {
333
- if (viewElement.parent && viewElement.parent.is('element', 'li')) {
334
- return writer.createElement(elementName, { listType: '' });
335
- }
336
- return null;
337
- },
338
- converterPriority: 'high'
339
- })
340
- .add(dispatcher => {
341
- dispatcher.on('element:li', listItemUpcastConverter());
342
- });
343
- if (!multiBlock) {
344
- editor.conversion.for('downcast')
345
- .elementToElement({
346
- model: 'listItem',
347
- view: 'p'
348
- });
349
- }
350
- editor.conversion.for('editingDowncast')
351
- .elementToElement({
352
- model: elementName,
353
- view: bogusParagraphCreator(attributeNames),
354
- converterPriority: 'high'
355
- })
356
- .add(dispatcher => {
357
- dispatcher.on('attribute', listItemDowncastConverter(attributeNames, this._downcastStrategies, model));
358
- dispatcher.on('remove', listItemDowncastRemoveConverter(model.schema));
359
- });
360
- editor.conversion.for('dataDowncast')
361
- .elementToElement({
362
- model: elementName,
363
- view: bogusParagraphCreator(attributeNames, { dataPipeline: true }),
364
- converterPriority: 'high'
365
- })
366
- .add(dispatcher => {
367
- dispatcher.on('attribute', listItemDowncastConverter(attributeNames, this._downcastStrategies, model, { dataPipeline: true }));
368
- });
369
- const modelToViewPositionMapper = createModelToViewPositionMapper(this._downcastStrategies, editor.editing.view);
370
- editor.editing.mapper.on('modelToViewPosition', modelToViewPositionMapper);
371
- editor.data.mapper.on('modelToViewPosition', modelToViewPositionMapper);
372
- this.listenTo(model.document, 'change:data', reconvertItemsOnDataChange(model, editor.editing, attributeNames, this), { priority: 'high' });
373
- // For LI verify if an ID of the attribute element is correct.
374
- this.on('checkAttributes:item', (evt, { viewElement, modelAttributes }) => {
375
- if (viewElement.id != modelAttributes.listItemId) {
376
- evt.return = true;
377
- evt.stop();
378
- }
379
- });
380
- // For UL and OL check if the name and ID of element is correct.
381
- this.on('checkAttributes:list', (evt, { viewElement, modelAttributes }) => {
382
- if (viewElement.name != getViewElementNameForListType(modelAttributes.listType) ||
383
- viewElement.id != getViewElementIdForListType(modelAttributes.listType, modelAttributes.listIndent)) {
384
- evt.return = true;
385
- evt.stop();
386
- }
387
- });
388
- }
389
- /**
390
- * Registers model post-fixers.
391
- */
392
- _setupModelPostFixing() {
393
- const model = this.editor.model;
394
- const attributeNames = this.getListAttributeNames();
395
- // Register list fixing.
396
- // First the low level handler.
397
- model.document.registerPostFixer(writer => modelChangePostFixer(model, writer, attributeNames, this));
398
- // Then the callbacks for the specific lists.
399
- // The indentation fixing must be the first one...
400
- this.on('postFixer', (evt, { listNodes, writer }) => {
401
- evt.return = fixListIndents(listNodes, writer) || evt.return;
402
- }, { priority: 'high' });
403
- // ...then the item ids... and after that other fixers that rely on the correct indentation and ids.
404
- this.on('postFixer', (evt, { listNodes, writer, seenIds }) => {
405
- evt.return = fixListItemIds(listNodes, seenIds, writer) || evt.return;
406
- }, { priority: 'high' });
407
- }
408
- /**
409
- * Integrates the feature with the clipboard via {@link module:engine/model/model~Model#insertContent} and
410
- * {@link module:engine/model/model~Model#getSelectedContent}.
411
- */
412
- _setupClipboardIntegration() {
413
- const model = this.editor.model;
414
- const clipboardPipeline = this.editor.plugins.get('ClipboardPipeline');
415
- this.listenTo(model, 'insertContent', createModelIndentPasteFixer(model), { priority: 'high' });
416
- // To enhance the UX, the editor should not copy list attributes to the clipboard if the selection
417
- // started and ended in the same list item.
418
- //
419
- // If the selection was enclosed in a single list item, there is a good chance the user did not want it
420
- // copied as a list item but plain blocks.
421
- //
422
- // This avoids pasting orphaned list items instead of paragraphs, for instance, straight into the root.
423
- //
424
- // ┌─────────────────────┬───────────────────┐
425
- // │ Selection │ Clipboard content │
426
- // ├─────────────────────┼───────────────────┤
427
- // │ [* <Widget />] │ <Widget /> │
428
- // ├─────────────────────┼───────────────────┤
429
- // │ [* Foo] │ Foo │
430
- // ├─────────────────────┼───────────────────┤
431
- // │ * Foo [bar] baz │ bar │
432
- // ├─────────────────────┼───────────────────┤
433
- // │ * Fo[o │ o │
434
- // │ ba]r │ ba │
435
- // ├─────────────────────┼───────────────────┤
436
- // │ * Fo[o │ * o │
437
- // │ * ba]r │ * ba │
438
- // ├─────────────────────┼───────────────────┤
439
- // │ [* Foo │ * Foo │
440
- // │ * bar] │ * bar │
441
- // └─────────────────────┴───────────────────┘
442
- //
443
- // See https://github.com/ckeditor/ckeditor5/issues/11608, https://github.com/ckeditor/ckeditor5/issues/14969
444
- this.listenTo(clipboardPipeline, 'outputTransformation', (evt, data) => {
445
- model.change(writer => {
446
- // Remove last block if it's empty.
447
- const allContentChildren = Array.from(data.content.getChildren());
448
- const lastItem = allContentChildren[allContentChildren.length - 1];
449
- if (allContentChildren.length > 1 && lastItem.is('element') && lastItem.isEmpty) {
450
- const contentChildrenExceptLastItem = allContentChildren.slice(0, -1);
451
- if (contentChildrenExceptLastItem.every(isListItemBlock)) {
452
- writer.remove(lastItem);
453
- }
454
- }
455
- // Copy/cut only content of a list item (for drag-drop move the whole list item).
456
- if (data.method == 'copy' || data.method == 'cut') {
457
- const allChildren = Array.from(data.content.getChildren());
458
- const isSingleListItemSelected = isSingleListItem(allChildren);
459
- if (isSingleListItemSelected) {
460
- removeListAttributes(allChildren, writer, this.getListAttributeNames());
461
- }
462
- }
463
- });
464
- });
465
- }
466
- /**
467
- * Informs editor accessibility features about keystrokes brought by the plugin.
468
- */
469
- _setupAccessibilityIntegration() {
470
- const editor = this.editor;
471
- const t = editor.t;
472
- editor.accessibility.addKeystrokeInfoGroup({
473
- id: 'list',
474
- label: t('Keystrokes that can be used in a list'),
475
- keystrokes: [
476
- {
477
- label: t('Increase list item indent'),
478
- keystroke: 'Tab'
479
- },
480
- {
481
- label: t('Decrease list item indent'),
482
- keystroke: 'Shift+Tab'
483
- }
484
- ]
485
- });
486
- }
487
- /**
488
- * Convert `listItemId` attribute to `data-list-item-id` attribute on the view element in both downcast pipelines.
489
- */
490
- _setupListItemIdConversionStrategy() {
491
- this.registerDowncastStrategy({
492
- scope: 'item',
493
- attributeName: 'listItemId',
494
- setAttributeOnDowncast(writer, attributeValue, viewElement, options) {
495
- if (options && (options.skipListItemIds || options.isClipboardPipeline)) {
496
- return;
497
- }
498
- writer.setAttribute('data-list-item-id', attributeValue, viewElement);
499
- }
500
- });
501
- }
502
- }
503
- /**
504
- * Post-fixer that reacts to changes on document and fixes incorrect model states (invalid `listItemId` and `listIndent` values).
505
- *
506
- * In the example below, there is a correct list structure.
507
- * Then the middle element is removed so the list structure will become incorrect:
508
- *
509
- * ```xml
510
- * <paragraph listType="bulleted" listItemId="a" listIndent=0>Item 1</paragraph>
511
- * <paragraph listType="bulleted" listItemId="b" listIndent=1>Item 2</paragraph> <--- this is removed.
512
- * <paragraph listType="bulleted" listItemId="c" listIndent=2>Item 3</paragraph>
513
- * ```
514
- *
515
- * The list structure after the middle element is removed:
516
- *
517
- * ```xml
518
- * <paragraph listType="bulleted" listItemId="a" listIndent=0>Item 1</paragraph>
519
- * <paragraph listType="bulleted" listItemId="c" listIndent=2>Item 3</paragraph>
520
- * ```
521
- *
522
- * Should become:
523
- *
524
- * ```xml
525
- * <paragraph listType="bulleted" listItemId="a" listIndent=0>Item 1</paragraph>
526
- * <paragraph listType="bulleted" listItemId="c" listIndent=1>Item 3</paragraph> <--- note that indent got post-fixed.
527
- * ```
528
- *
529
- * @param model The data model.
530
- * @param writer The writer to do changes with.
531
- * @param attributeNames The list of all model list attributes (including registered strategies).
532
- * @param ListEditing The document list editing plugin.
533
- * @returns `true` if any change has been applied, `false` otherwise.
534
- */
535
- function modelChangePostFixer(model, writer, attributeNames, listEditing) {
536
- const changes = model.document.differ.getChanges();
537
- const visited = new Set();
538
- const itemToListHead = new Set();
539
- const multiBlock = listEditing.editor.config.get('list.multiBlock');
540
- let applied = false;
541
- for (const entry of changes) {
542
- if (entry.type == 'insert' && entry.name != '$text') {
543
- const item = entry.position.nodeAfter;
544
- // Remove attributes in case of renamed element.
545
- if (!model.schema.checkAttribute(item, 'listItemId')) {
546
- for (const attributeName of Array.from(item.getAttributeKeys())) {
547
- if (attributeNames.includes(attributeName)) {
548
- writer.removeAttribute(attributeName, item);
549
- applied = true;
550
- }
551
- }
552
- }
553
- findAndAddListHeadToMap(entry.position, itemToListHead, visited);
554
- // Insert of a non-list item - check if there is a list after it.
555
- if (!entry.attributes.has('listItemId')) {
556
- findAndAddListHeadToMap(entry.position.getShiftedBy(entry.length), itemToListHead, visited);
557
- }
558
- // Check if there is no nested list.
559
- for (const { item: innerItem, previousPosition } of model.createRangeIn(item)) {
560
- if (isListItemBlock(innerItem)) {
561
- findAndAddListHeadToMap(previousPosition, itemToListHead, visited);
562
- }
563
- }
564
- }
565
- // Removed list item or block adjacent to a list.
566
- else if (entry.type == 'remove') {
567
- findAndAddListHeadToMap(entry.position, itemToListHead, visited);
568
- }
569
- // Changed list item indent or type.
570
- else if (entry.type == 'attribute' && attributeNames.includes(entry.attributeKey)) {
571
- findAndAddListHeadToMap(entry.range.start, itemToListHead, visited);
572
- if (entry.attributeNewValue === null) {
573
- findAndAddListHeadToMap(entry.range.start.getShiftedBy(1), itemToListHead, visited);
574
- }
575
- }
576
- // Make sure that there is no left over listItem element without attributes or a block with list attributes that is not a listItem.
577
- if (!multiBlock && entry.type == 'attribute' && LIST_BASE_ATTRIBUTES.includes(entry.attributeKey)) {
578
- const element = entry.range.start.nodeAfter;
579
- if (entry.attributeNewValue === null && element && element.is('element', 'listItem')) {
580
- writer.rename(element, 'paragraph');
581
- applied = true;
582
- }
583
- else if (entry.attributeOldValue === null && element && element.is('element') && element.name != 'listItem') {
584
- writer.rename(element, 'listItem');
585
- applied = true;
586
- }
587
- }
588
- }
589
- // Make sure that IDs are not shared by split list.
590
- const seenIds = new Set();
591
- for (const listHead of itemToListHead.values()) {
592
- applied = listEditing.fire('postFixer', {
593
- listNodes: new ListBlocksIterable(listHead),
594
- listHead,
595
- writer,
596
- seenIds
597
- }) || applied;
598
- }
599
- return applied;
600
- }
601
- /**
602
- * A fixer for pasted content that includes list items.
603
- *
604
- * It fixes indentation of pasted list items so the pasted items match correctly to the context they are pasted into.
605
- *
606
- * Example:
607
- *
608
- * ```xml
609
- * <paragraph listType="bulleted" listItemId="a" listIndent="0">A</paragraph>
610
- * <paragraph listType="bulleted" listItemId="b" listIndent="1">B^</paragraph>
611
- * // At ^ paste: <paragraph listType="numbered" listItemId="x" listIndent="0">X</paragraph>
612
- * // <paragraph listType="numbered" listItemId="y" listIndent="1">Y</paragraph>
613
- * <paragraph listType="bulleted" listItemId="c" listIndent="2">C</paragraph>
614
- * ```
615
- *
616
- * Should become:
617
- *
618
- * ```xml
619
- * <paragraph listType="bulleted" listItemId="a" listIndent="0">A</paragraph>
620
- * <paragraph listType="bulleted" listItemId="b" listIndent="1">BX</paragraph>
621
- * <paragraph listType="bulleted" listItemId="y" listIndent="2">Y/paragraph>
622
- * <paragraph listType="bulleted" listItemId="c" listIndent="2">C</paragraph>
623
- * ```
624
- */
625
- function createModelIndentPasteFixer(model) {
626
- return (evt, [content, selectable]) => {
627
- const items = content.is('documentFragment') ?
628
- Array.from(content.getChildren()) :
629
- [content];
630
- if (!items.length) {
631
- return;
632
- }
633
- const selection = selectable ?
634
- model.createSelection(selectable) :
635
- model.document.selection;
636
- const position = selection.getFirstPosition();
637
- // Get a reference list item. Attributes of the inserted list items will be fixed according to that item.
638
- let refItem;
639
- if (isListItemBlock(position.parent)) {
640
- refItem = position.parent;
641
- }
642
- else if (isListItemBlock(position.nodeBefore) && isListItemBlock(position.nodeAfter)) {
643
- refItem = position.nodeBefore;
644
- }
645
- else {
646
- return; // Content is not copied into a list.
647
- }
648
- model.change(writer => {
649
- const refType = refItem.getAttribute('listType');
650
- const refIndent = refItem.getAttribute('listIndent');
651
- const firstElementIndent = items[0].getAttribute('listIndent') || 0;
652
- const indentDiff = Math.max(refIndent - firstElementIndent, 0);
653
- for (const item of items) {
654
- const isListItem = isListItemBlock(item);
655
- if (refItem.is('element', 'listItem') && item.is('element', 'paragraph')) {
656
- /**
657
- * When paragraphs or a plain text list is pasted into a simple list, convert
658
- * the `<paragraphs>' to `<listItem>' to avoid breaking the target list.
659
- *
660
- * See https://github.com/ckeditor/ckeditor5/issues/13826.
661
- */
662
- writer.rename(item, 'listItem');
663
- }
664
- writer.setAttributes({
665
- listIndent: (isListItem ? item.getAttribute('listIndent') : 0) + indentDiff,
666
- listItemId: isListItem ? item.getAttribute('listItemId') : ListItemUid.next(),
667
- listType: refType
668
- }, item);
669
- }
670
- });
671
- };
672
- }
673
- /**
674
- * Decides whether the merge should be accompanied by the model's `deleteContent()`, for instance, to get rid of the inline
675
- * content in the selection or take advantage of the heuristics in `deleteContent()` that helps convert lists into paragraphs
676
- * in certain cases.
677
- */
678
- function shouldMergeOnBlocksContentLevel(model, direction) {
679
- const selection = model.document.selection;
680
- if (!selection.isCollapsed) {
681
- return !getSelectedBlockObject(model);
682
- }
683
- if (direction === 'forward') {
684
- return true;
685
- }
686
- const firstPosition = selection.getFirstPosition();
687
- const positionParent = firstPosition.parent;
688
- const previousSibling = positionParent.previousSibling;
689
- if (model.schema.isObject(previousSibling)) {
690
- return false;
691
- }
692
- if (previousSibling.isEmpty) {
693
- return true;
694
- }
695
- return isSingleListItem([positionParent, previousSibling]);
696
- }