@ckeditor/ckeditor5-list 40.2.0 → 41.0.0
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.
- package/CHANGELOG.md +25 -25
- package/LICENSE.md +1 -1
- package/build/list.js +2 -2
- package/build/translations/ug.js +1 -1
- package/ckeditor5-metadata.json +34 -34
- package/lang/translations/ar.po +1 -1
- package/lang/translations/ast.po +1 -1
- package/lang/translations/az.po +1 -1
- package/lang/translations/bg.po +1 -1
- package/lang/translations/bn.po +1 -1
- package/lang/translations/ca.po +1 -1
- package/lang/translations/cs.po +1 -1
- package/lang/translations/da.po +1 -1
- package/lang/translations/de-ch.po +1 -1
- package/lang/translations/de.po +1 -1
- package/lang/translations/el.po +1 -1
- package/lang/translations/en-au.po +1 -1
- package/lang/translations/en-gb.po +1 -1
- package/lang/translations/en.po +1 -1
- package/lang/translations/eo.po +1 -1
- package/lang/translations/es.po +1 -1
- package/lang/translations/et.po +1 -1
- package/lang/translations/eu.po +1 -1
- package/lang/translations/fa.po +1 -1
- package/lang/translations/fi.po +1 -1
- package/lang/translations/fr.po +1 -1
- package/lang/translations/gl.po +1 -1
- package/lang/translations/he.po +1 -1
- package/lang/translations/hi.po +1 -1
- package/lang/translations/hr.po +1 -1
- package/lang/translations/hu.po +1 -1
- package/lang/translations/id.po +1 -1
- package/lang/translations/it.po +1 -1
- package/lang/translations/ja.po +1 -1
- package/lang/translations/jv.po +1 -1
- package/lang/translations/km.po +1 -1
- package/lang/translations/kn.po +1 -1
- package/lang/translations/ko.po +1 -1
- package/lang/translations/ku.po +1 -1
- package/lang/translations/lt.po +1 -1
- package/lang/translations/lv.po +1 -1
- package/lang/translations/ms.po +1 -1
- package/lang/translations/nb.po +1 -1
- package/lang/translations/ne.po +1 -1
- package/lang/translations/nl.po +1 -1
- package/lang/translations/no.po +1 -1
- package/lang/translations/pl.po +1 -1
- package/lang/translations/pt-br.po +1 -1
- package/lang/translations/pt.po +1 -1
- package/lang/translations/ro.po +1 -1
- package/lang/translations/ru.po +1 -1
- package/lang/translations/si.po +1 -1
- package/lang/translations/sk.po +1 -1
- package/lang/translations/sq.po +1 -1
- package/lang/translations/sr-latn.po +1 -1
- package/lang/translations/sr.po +1 -1
- package/lang/translations/sv.po +1 -1
- package/lang/translations/th.po +1 -1
- package/lang/translations/tk.po +1 -1
- package/lang/translations/tr.po +1 -1
- package/lang/translations/tt.po +1 -1
- package/lang/translations/ug.po +8 -8
- package/lang/translations/uk.po +1 -1
- package/lang/translations/ur.po +1 -1
- package/lang/translations/uz.po +1 -1
- package/lang/translations/vi.po +1 -1
- package/lang/translations/zh-cn.po +1 -1
- package/lang/translations/zh.po +1 -1
- package/package.json +3 -2
- package/src/augmentation.d.ts +29 -28
- package/src/augmentation.js +1 -1
- package/src/documentlist.d.ts +9 -7
- package/src/documentlist.js +18 -7
- package/src/documentlistproperties.d.ts +9 -8
- package/src/documentlistproperties.js +18 -8
- package/src/index.d.ts +39 -37
- package/src/index.js +32 -23
- package/src/legacylist/legacyconverters.d.ts +196 -0
- package/src/legacylist/legacyconverters.js +905 -0
- package/src/{list/indentcommand.d.ts → legacylist/legacyindentcommand.d.ts} +4 -4
- package/src/{list/indentcommand.js → legacylist/legacyindentcommand.js} +5 -5
- package/src/{documentlist/documentlistcommand.d.ts → legacylist/legacylistcommand.d.ts} +5 -30
- package/src/legacylist/legacylistcommand.js +274 -0
- package/src/legacylist/legacylistediting.d.ts +32 -0
- package/src/legacylist/legacylistediting.js +161 -0
- package/src/legacylist/legacylistutils.d.ts +41 -0
- package/src/legacylist/legacylistutils.js +46 -0
- package/src/legacylist/legacyutils.d.ts +101 -0
- package/src/legacylist/legacyutils.js +347 -0
- package/src/legacylist.d.ts +26 -0
- package/src/legacylist.js +30 -0
- package/src/legacylistproperties/legacylistpropertiesediting.d.ts +72 -0
- package/src/legacylistproperties/legacylistpropertiesediting.js +696 -0
- package/src/legacylistproperties/legacylistreversedcommand.d.ts +38 -0
- package/src/legacylistproperties/legacylistreversedcommand.js +52 -0
- package/src/{documentlistproperties/documentliststartcommand.d.ts → legacylistproperties/legacyliststartcommand.d.ts} +6 -7
- package/src/legacylistproperties/legacyliststartcommand.js +51 -0
- package/src/{documentlistproperties/documentliststylecommand.d.ts → legacylistproperties/legacyliststylecommand.d.ts} +14 -19
- package/src/{documentlistproperties/documentliststylecommand.js → legacylistproperties/legacyliststylecommand.js} +22 -36
- package/src/legacylistproperties.d.ts +27 -0
- package/src/legacylistproperties.js +31 -0
- package/src/{tododocumentlist/checktododocumentlistcommand.d.ts → legacytodolist/legacychecktodolistcommand.d.ts} +17 -14
- package/src/{tododocumentlist/checktododocumentlistcommand.js → legacytodolist/legacychecktodolistcommand.js} +34 -40
- package/src/{todolist/todolistconverters.d.ts → legacytodolist/legacytodolistconverters.d.ts} +9 -8
- package/src/{todolist/todolistconverters.js → legacytodolist/legacytodolistconverters.js} +6 -5
- package/src/{tododocumentlist/tododocumentlistediting.d.ts → legacytodolist/legacytodolistediting.d.ts} +9 -8
- package/src/legacytodolist/legacytodolistediting.js +161 -0
- package/src/legacytodolist.d.ts +27 -0
- package/src/legacytodolist.js +31 -0
- package/src/{documentlist → list}/adjacentlistssupport.d.ts +2 -2
- package/src/{documentlist → list}/adjacentlistssupport.js +9 -9
- package/src/list/converters.d.ts +41 -172
- package/src/list/converters.js +357 -821
- package/src/list/listcommand.d.ts +28 -3
- package/src/list/listcommand.js +81 -205
- package/src/list/listediting.d.ts +189 -9
- package/src/list/listediting.js +592 -107
- package/src/{documentlist/documentlistindentcommand.d.ts → list/listindentcommand.d.ts} +10 -10
- package/src/{documentlist/documentlistindentcommand.js → list/listindentcommand.js} +7 -7
- package/src/{documentlist/documentlistmergecommand.d.ts → list/listmergecommand.d.ts} +10 -10
- package/src/{documentlist/documentlistmergecommand.js → list/listmergecommand.js} +7 -7
- package/src/{documentlist/documentlistsplitcommand.d.ts → list/listsplitcommand.d.ts} +10 -10
- package/src/{documentlist/documentlistsplitcommand.js → list/listsplitcommand.js} +5 -5
- package/src/list/listui.d.ts +2 -2
- package/src/list/listui.js +5 -7
- package/src/list/listutils.d.ts +22 -17
- package/src/list/listutils.js +24 -20
- package/src/{documentlist → list}/utils/listwalker.d.ts +5 -5
- package/src/{documentlist → list}/utils/listwalker.js +4 -4
- package/src/{documentlist → list}/utils/model.d.ts +4 -4
- package/src/{documentlist → list}/utils/model.js +3 -3
- package/src/{documentlist → list}/utils/postfixers.d.ts +5 -5
- package/src/{documentlist → list}/utils/postfixers.js +3 -3
- package/src/{documentlist → list}/utils/view.d.ts +3 -3
- package/src/{documentlist → list}/utils/view.js +1 -1
- package/src/list/utils.d.ts +2 -96
- package/src/list/utils.js +2 -342
- package/src/list.d.ts +6 -6
- package/src/list.js +6 -6
- package/src/listconfig.d.ts +10 -10
- package/src/listconfig.js +1 -1
- package/src/{documentlistproperties → listproperties}/converters.d.ts +5 -5
- package/src/{documentlistproperties → listproperties}/converters.js +1 -1
- package/src/listproperties/listpropertiesediting.d.ts +56 -40
- package/src/listproperties/listpropertiesediting.js +145 -575
- package/src/listproperties/listpropertiesui.d.ts +2 -2
- package/src/listproperties/listpropertiesui.js +6 -8
- package/src/{documentlistproperties/documentlistpropertiesutils.d.ts → listproperties/listpropertiesutils.d.ts} +5 -5
- package/src/{documentlistproperties/documentlistpropertiesutils.js → listproperties/listpropertiesutils.js} +6 -6
- package/src/listproperties/listreversedcommand.d.ts +4 -6
- package/src/listproperties/listreversedcommand.js +17 -14
- package/src/listproperties/liststartcommand.d.ts +4 -3
- package/src/listproperties/liststartcommand.js +17 -11
- package/src/listproperties/liststylecommand.d.ts +16 -11
- package/src/listproperties/liststylecommand.js +33 -19
- package/src/listproperties/ui/listpropertiesview.d.ts +5 -5
- package/src/listproperties/ui/listpropertiesview.js +3 -3
- package/src/{documentlistproperties → listproperties}/utils/style.d.ts +1 -1
- package/src/{documentlistproperties → listproperties}/utils/style.js +2 -2
- package/src/listproperties.d.ts +6 -5
- package/src/listproperties.js +6 -5
- package/src/tododocumentlist.d.ts +9 -8
- package/src/tododocumentlist.js +18 -8
- package/src/todolist/checktodolistcommand.d.ts +11 -14
- package/src/todolist/checktodolistcommand.js +37 -31
- package/src/{tododocumentlist → todolist}/todocheckboxchangeobserver.d.ts +6 -6
- package/src/{tododocumentlist → todolist}/todocheckboxchangeobserver.js +3 -3
- package/src/todolist/todolistediting.d.ts +5 -6
- package/src/todolist/todolistediting.js +314 -76
- package/src/todolist/todolistui.d.ts +2 -2
- package/src/todolist/todolistui.js +4 -5
- package/src/todolist.d.ts +6 -6
- package/src/todolist.js +6 -6
- package/theme/documentlist.css +1 -1
- package/theme/list.css +1 -1
- package/theme/listproperties.css +1 -1
- package/theme/liststyles.css +1 -1
- package/theme/todolist.css +1 -1
- package/src/documentlist/converters.d.ts +0 -65
- package/src/documentlist/converters.js +0 -441
- package/src/documentlist/documentlistcommand.js +0 -150
- package/src/documentlist/documentlistediting.d.ts +0 -212
- package/src/documentlist/documentlistediting.js +0 -646
- package/src/documentlist/documentlistutils.d.ts +0 -46
- package/src/documentlist/documentlistutils.js +0 -50
- package/src/documentlistproperties/documentlistpropertiesediting.d.ts +0 -88
- package/src/documentlistproperties/documentlistpropertiesediting.js +0 -266
- package/src/documentlistproperties/documentlistreversedcommand.d.ts +0 -36
- package/src/documentlistproperties/documentlistreversedcommand.js +0 -55
- package/src/documentlistproperties/documentliststartcommand.js +0 -57
- package/src/liststyle.d.ts +0 -28
- package/src/liststyle.js +0 -36
- package/src/tododocumentlist/tododocumentlistediting.js +0 -399
- package/theme/icons/bulletedlist.svg +0 -1
- package/theme/icons/numberedlist.svg +0 -1
- package/theme/icons/todolist.svg +0 -1
package/src/list/converters.js
CHANGED
|
@@ -1,905 +1,441 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* @module list/list/converters
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { UpcastWriter } from 'ckeditor5/src/engine.js';
|
|
9
|
+
import { getAllListItemBlocks, getListItemBlocks, isListItemBlock, isFirstBlockOfListItem, ListItemUid } from './utils/model.js';
|
|
10
|
+
import { createListElement, createListItemElement, getIndent, isListView, isListItemView } from './utils/view.js';
|
|
11
|
+
import ListWalker, { iterateSiblingListBlocks } from './utils/listwalker.js';
|
|
12
|
+
import { findAndAddListHeadToMap } from './utils/postfixers.js';
|
|
10
13
|
/**
|
|
11
|
-
*
|
|
14
|
+
* Returns the upcast converter for list items. It's supposed to work after the block converters (content inside list items) are converted.
|
|
12
15
|
*
|
|
13
|
-
*
|
|
14
|
-
* position, and merges the list with surrounding lists (if available).
|
|
15
|
-
*
|
|
16
|
-
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert
|
|
17
|
-
* @param model Model instance.
|
|
16
|
+
* @internal
|
|
18
17
|
*/
|
|
19
|
-
export function
|
|
18
|
+
export function listItemUpcastConverter() {
|
|
20
19
|
return (evt, data, conversionApi) => {
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
const { writer, schema } = conversionApi;
|
|
21
|
+
if (!data.modelRange) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const items = Array.from(data.modelRange.getItems({ shallow: true }))
|
|
25
|
+
.filter((item) => schema.checkAttribute(item, 'listItemId'));
|
|
26
|
+
if (!items.length) {
|
|
25
27
|
return;
|
|
26
28
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
29
|
+
const listItemId = ListItemUid.next();
|
|
30
|
+
const listIndent = getIndent(data.viewItem);
|
|
31
|
+
let listType = data.viewItem.parent && data.viewItem.parent.is('element', 'ol') ? 'numbered' : 'bulleted';
|
|
32
|
+
// Preserve list type if was already set (for example by to-do list feature).
|
|
33
|
+
const firstItemListType = items[0].getAttribute('listType');
|
|
34
|
+
if (firstItemListType) {
|
|
35
|
+
listType = firstItemListType;
|
|
36
|
+
}
|
|
37
|
+
const attributes = {
|
|
38
|
+
listItemId,
|
|
39
|
+
listIndent,
|
|
40
|
+
listType
|
|
41
|
+
};
|
|
42
|
+
for (const item of items) {
|
|
43
|
+
// Set list attributes only on same level items, those nested deeper are already handled by the recursive conversion.
|
|
44
|
+
if (!item.hasAttribute('listItemId')) {
|
|
45
|
+
writer.setAttributes(attributes, item);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (items.length > 1) {
|
|
49
|
+
// Make sure that list item that contain only nested list will preserve paragraph for itself:
|
|
50
|
+
// <ul>
|
|
51
|
+
// <li>
|
|
52
|
+
// <p></p> <-- this one must be kept
|
|
53
|
+
// <ul>
|
|
54
|
+
// <li></li>
|
|
55
|
+
// </ul>
|
|
56
|
+
// </li>
|
|
57
|
+
// </ul>
|
|
58
|
+
if (items[1].getAttribute('listItemId') != attributes.listItemId) {
|
|
59
|
+
conversionApi.keepEmptyElement(items[0]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
33
62
|
};
|
|
34
63
|
}
|
|
35
64
|
/**
|
|
36
|
-
*
|
|
65
|
+
* Returns the upcast converter for the `<ul>` and `<ol>` view elements that cleans the input view of garbage.
|
|
66
|
+
* This is mostly to clean whitespaces from between the `<li>` view elements inside the view list element. However,
|
|
67
|
+
* incorrect data can also be cleared if the view was incorrect.
|
|
37
68
|
*
|
|
38
|
-
* @
|
|
39
|
-
* @param model Model instance.
|
|
40
|
-
* @returns Returns a conversion callback.
|
|
69
|
+
* @internal
|
|
41
70
|
*/
|
|
42
|
-
export function
|
|
71
|
+
export function listUpcastCleanList() {
|
|
43
72
|
return (evt, data, conversionApi) => {
|
|
44
|
-
|
|
45
|
-
const viewStart = viewPosition.getLastMatchingPosition(value => !value.item.is('element', 'li'));
|
|
46
|
-
const viewItem = viewStart.nodeAfter;
|
|
47
|
-
const viewWriter = conversionApi.writer;
|
|
48
|
-
// 1. Break the container after and before the list item.
|
|
49
|
-
// This will create a view list with one view list item - the one to remove.
|
|
50
|
-
viewWriter.breakContainer(viewWriter.createPositionBefore(viewItem));
|
|
51
|
-
viewWriter.breakContainer(viewWriter.createPositionAfter(viewItem));
|
|
52
|
-
// 2. Remove the list with the item to remove.
|
|
53
|
-
const viewList = viewItem.parent;
|
|
54
|
-
const viewListPrev = viewList.previousSibling;
|
|
55
|
-
const removeRange = viewWriter.createRangeOn(viewList);
|
|
56
|
-
const removed = viewWriter.remove(removeRange);
|
|
57
|
-
// 3. Merge the whole created by breaking and removing the list.
|
|
58
|
-
if (viewListPrev && viewListPrev.nextSibling) {
|
|
59
|
-
mergeViewLists(viewWriter, viewListPrev, viewListPrev.nextSibling);
|
|
60
|
-
}
|
|
61
|
-
// 4. Bring back nested list that was in the removed <li>.
|
|
62
|
-
const modelItem = conversionApi.mapper.toModelElement(viewItem);
|
|
63
|
-
hoistNestedLists(modelItem.getAttribute('listIndent') + 1, data.position, removeRange.start, viewItem, conversionApi, model);
|
|
64
|
-
// 5. Unbind removed view item and all children.
|
|
65
|
-
for (const child of viewWriter.createRangeIn(removed).getItems()) {
|
|
66
|
-
conversionApi.mapper.unbindViewElement(child);
|
|
67
|
-
}
|
|
68
|
-
evt.stop();
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* A model-to-view converter for the `type` attribute change on the `listItem` model element.
|
|
73
|
-
*
|
|
74
|
-
* This change means that the `<li>` element parent changes from `<ul>` to `<ol>` (or vice versa). This is accomplished
|
|
75
|
-
* by breaking view elements and changing their name. The next {@link module:list/list/converters~modelViewMergeAfterChangeType}
|
|
76
|
-
* converter will attempt to merge split nodes.
|
|
77
|
-
*
|
|
78
|
-
* Splitting this conversion into 2 steps makes it possible to add an additional conversion in the middle.
|
|
79
|
-
* Check {@link module:list/todolist/todolistconverters~modelViewChangeType} to see an example of it.
|
|
80
|
-
*
|
|
81
|
-
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute
|
|
82
|
-
*/
|
|
83
|
-
export const modelViewChangeType = (evt, data, conversionApi) => {
|
|
84
|
-
if (!conversionApi.consumable.test(data.item, evt.name)) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const viewItem = conversionApi.mapper.toViewElement(data.item);
|
|
88
|
-
const viewWriter = conversionApi.writer;
|
|
89
|
-
// Break the container after and before the list item.
|
|
90
|
-
// This will create a view list with one view list item -- the one that changed type.
|
|
91
|
-
viewWriter.breakContainer(viewWriter.createPositionBefore(viewItem));
|
|
92
|
-
viewWriter.breakContainer(viewWriter.createPositionAfter(viewItem));
|
|
93
|
-
// Change name of the view list that holds the changed view item.
|
|
94
|
-
// We cannot just change name property, because that would not render properly.
|
|
95
|
-
const viewList = viewItem.parent;
|
|
96
|
-
const listName = data.attributeNewValue == 'numbered' ? 'ol' : 'ul';
|
|
97
|
-
viewWriter.rename(listName, viewList);
|
|
98
|
-
};
|
|
99
|
-
/**
|
|
100
|
-
* A model-to-view converter that attempts to merge nodes split by {@link module:list/list/converters~modelViewChangeType}.
|
|
101
|
-
*
|
|
102
|
-
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute
|
|
103
|
-
*/
|
|
104
|
-
export const modelViewMergeAfterChangeType = (evt, data, conversionApi) => {
|
|
105
|
-
conversionApi.consumable.consume(data.item, evt.name);
|
|
106
|
-
const viewItem = conversionApi.mapper.toViewElement(data.item);
|
|
107
|
-
const viewList = viewItem.parent;
|
|
108
|
-
const viewWriter = conversionApi.writer;
|
|
109
|
-
// Merge the changed view list with other lists, if possible.
|
|
110
|
-
mergeViewLists(viewWriter, viewList, viewList.nextSibling);
|
|
111
|
-
mergeViewLists(viewWriter, viewList.previousSibling, viewList);
|
|
112
|
-
};
|
|
113
|
-
/**
|
|
114
|
-
* A model-to-view converter for the `listIndent` attribute change on the `listItem` model element.
|
|
115
|
-
*
|
|
116
|
-
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute
|
|
117
|
-
* @param model Model instance.
|
|
118
|
-
* @returns Returns a conversion callback.
|
|
119
|
-
*/
|
|
120
|
-
export function modelViewChangeIndent(model) {
|
|
121
|
-
return (evt, data, conversionApi) => {
|
|
122
|
-
if (!conversionApi.consumable.consume(data.item, 'attribute:listIndent')) {
|
|
73
|
+
if (!conversionApi.consumable.test(data.viewItem, { name: true })) {
|
|
123
74
|
return;
|
|
124
75
|
}
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
viewWriter.breakContainer(viewWriter.createPositionAfter(viewItem));
|
|
131
|
-
// 2. Extract view list with changed view list item and merge "hole" possibly created by breaking and removing elements.
|
|
132
|
-
const viewList = viewItem.parent;
|
|
133
|
-
const viewListPrev = viewList.previousSibling;
|
|
134
|
-
const removeRange = viewWriter.createRangeOn(viewList);
|
|
135
|
-
viewWriter.remove(removeRange);
|
|
136
|
-
if (viewListPrev && viewListPrev.nextSibling) {
|
|
137
|
-
mergeViewLists(viewWriter, viewListPrev, viewListPrev.nextSibling);
|
|
138
|
-
}
|
|
139
|
-
// 3. Bring back nested list that was in the removed <li>.
|
|
140
|
-
hoistNestedLists(data.attributeOldValue + 1, data.range.start, removeRange.start, viewItem, conversionApi, model);
|
|
141
|
-
// 4. Inject view list like it is newly inserted.
|
|
142
|
-
injectViewList(data.item, viewItem, conversionApi, model);
|
|
143
|
-
// 5. Consume insertion of children inside the item. They are already handled by re-building the item in view.
|
|
144
|
-
for (const child of data.item.getChildren()) {
|
|
145
|
-
conversionApi.consumable.consume(child, 'insert');
|
|
76
|
+
const viewWriter = new UpcastWriter(data.viewItem.document);
|
|
77
|
+
for (const child of Array.from(data.viewItem.getChildren())) {
|
|
78
|
+
if (!isListItemView(child) && !isListView(child)) {
|
|
79
|
+
viewWriter.remove(child);
|
|
80
|
+
}
|
|
146
81
|
}
|
|
147
82
|
};
|
|
148
83
|
}
|
|
149
84
|
/**
|
|
150
|
-
*
|
|
151
|
-
* insert change of every model item, and should be fired before the actual converter. The converter checks whether the inserted
|
|
152
|
-
* model item is a non-`listItem` element. If it is, and it is inserted inside a view list, the converter breaks the
|
|
153
|
-
* list so the model element is inserted to the view parent element corresponding to its model parent element.
|
|
154
|
-
*
|
|
155
|
-
* The converter prevents such situations:
|
|
156
|
-
*
|
|
157
|
-
* ```xml
|
|
158
|
-
* // Model: // View:
|
|
159
|
-
* <listItem>foo</listItem> <ul>
|
|
160
|
-
* <listItem>bar</listItem> <li>foo</li>
|
|
161
|
-
* <li>bar</li>
|
|
162
|
-
* </ul>
|
|
85
|
+
* Returns a model document change:data event listener that triggers conversion of related items if needed.
|
|
163
86
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert
|
|
87
|
+
* @internal
|
|
88
|
+
* @param model The editor model.
|
|
89
|
+
* @param editing The editing controller.
|
|
90
|
+
* @param attributeNames The list of all model list attributes (including registered strategies).
|
|
91
|
+
* @param listEditing The document list editing plugin.
|
|
171
92
|
*/
|
|
172
|
-
export
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// 1.1.3 --------
|
|
189
|
-
// 1.1.3.1 --------
|
|
190
|
-
// 1.2 --------
|
|
191
|
-
// 1.2.1 --------
|
|
192
|
-
// 2 --------
|
|
193
|
-
//
|
|
194
|
-
// Insert paragraph after item 1.1.1:
|
|
195
|
-
//
|
|
196
|
-
// 1 --------
|
|
197
|
-
// 1.1 --------
|
|
198
|
-
// 1.1.1 --------
|
|
199
|
-
//
|
|
200
|
-
// Lorem ipsum.
|
|
201
|
-
//
|
|
202
|
-
// 1.1.2 --------
|
|
203
|
-
// 1.1.3 --------
|
|
204
|
-
// 1.1.3.1 --------
|
|
205
|
-
// 1.2 --------
|
|
206
|
-
// 1.2.1 --------
|
|
207
|
-
// 2 --------
|
|
208
|
-
//
|
|
209
|
-
// In this case 1.1.2 has to become beginning of a new list.
|
|
210
|
-
// We need to break list before 1.1.2 (obvious), then we need to break list also before 1.2.
|
|
211
|
-
// Then we need to move those broken pieces one after another and merge:
|
|
212
|
-
//
|
|
213
|
-
// 1 --------
|
|
214
|
-
// 1.1 --------
|
|
215
|
-
// 1.1.1 --------
|
|
216
|
-
//
|
|
217
|
-
// Lorem ipsum.
|
|
218
|
-
//
|
|
219
|
-
// 1.1.2 --------
|
|
220
|
-
// 1.1.3 --------
|
|
221
|
-
// 1.1.3.1 --------
|
|
222
|
-
// 1.2 --------
|
|
223
|
-
// 1.2.1 --------
|
|
224
|
-
// 2 --------
|
|
225
|
-
//
|
|
226
|
-
while (viewPosition.parent.name == 'ul' || viewPosition.parent.name == 'ol') {
|
|
227
|
-
viewPosition = viewWriter.breakContainer(viewPosition);
|
|
228
|
-
if (viewPosition.parent.name != 'li') {
|
|
229
|
-
break;
|
|
93
|
+
export function reconvertItemsOnDataChange(model, editing, attributeNames, listEditing) {
|
|
94
|
+
return () => {
|
|
95
|
+
const changes = model.document.differ.getChanges();
|
|
96
|
+
const itemsToRefresh = [];
|
|
97
|
+
const itemToListHead = new Map();
|
|
98
|
+
const changedItems = new Set();
|
|
99
|
+
for (const entry of changes) {
|
|
100
|
+
if (entry.type == 'insert' && entry.name != '$text') {
|
|
101
|
+
findAndAddListHeadToMap(entry.position, itemToListHead);
|
|
102
|
+
// Insert of a non-list item.
|
|
103
|
+
if (!entry.attributes.has('listItemId')) {
|
|
104
|
+
findAndAddListHeadToMap(entry.position.getShiftedBy(entry.length), itemToListHead);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
changedItems.add(entry.position.nodeAfter);
|
|
108
|
+
}
|
|
230
109
|
}
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const removeEnd = viewWriter.createPositionAt(viewPosition.parent, 'end');
|
|
235
|
-
// Don't remove if there is nothing to remove.
|
|
236
|
-
if (!removeStart.isEqual(removeEnd)) {
|
|
237
|
-
const removed = viewWriter.remove(viewWriter.createRange(removeStart, removeEnd));
|
|
238
|
-
lists.push(removed);
|
|
110
|
+
// Removed list item.
|
|
111
|
+
else if (entry.type == 'remove' && entry.attributes.has('listItemId')) {
|
|
112
|
+
findAndAddListHeadToMap(entry.position, itemToListHead);
|
|
239
113
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
114
|
+
// Changed list attribute.
|
|
115
|
+
else if (entry.type == 'attribute') {
|
|
116
|
+
const item = entry.range.start.nodeAfter;
|
|
117
|
+
if (attributeNames.includes(entry.attributeKey)) {
|
|
118
|
+
findAndAddListHeadToMap(entry.range.start, itemToListHead);
|
|
119
|
+
if (entry.attributeNewValue === null) {
|
|
120
|
+
findAndAddListHeadToMap(entry.range.start.getShiftedBy(1), itemToListHead);
|
|
121
|
+
// Check if paragraph should be converted from bogus to plain paragraph.
|
|
122
|
+
if (doesItemBlockRequiresRefresh(item)) {
|
|
123
|
+
itemsToRefresh.push(item);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
changedItems.add(item);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (isListItemBlock(item)) {
|
|
131
|
+
// Some other attribute was changed on the list item,
|
|
132
|
+
// check if paragraph does not need to be converted to bogus or back.
|
|
133
|
+
if (doesItemBlockRequiresRefresh(item)) {
|
|
134
|
+
itemsToRefresh.push(item);
|
|
255
135
|
}
|
|
256
136
|
}
|
|
257
137
|
}
|
|
258
|
-
// Merge last inserted list with element after it.
|
|
259
|
-
mergeViewLists(viewWriter, viewPosition.nodeBefore, viewPosition.nodeAfter);
|
|
260
138
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* A special model-to-view converter introduced by the {@link module:list/list~List list feature}. This converter takes care of
|
|
265
|
-
* merging view lists after something is removed or moved from near them.
|
|
266
|
-
*
|
|
267
|
-
* Example:
|
|
268
|
-
*
|
|
269
|
-
* ```xml
|
|
270
|
-
* // Model: // View:
|
|
271
|
-
* <listItem>foo</listItem> <ul><li>foo</li></ul>
|
|
272
|
-
* <paragraph>xxx</paragraph> <p>xxx</p>
|
|
273
|
-
* <listItem>bar</listItem> <ul><li>bar</li></ul>
|
|
274
|
-
*
|
|
275
|
-
* // After change: // Correct view guaranteed by this converter:
|
|
276
|
-
* <listItem>foo</listItem> <ul>
|
|
277
|
-
* <listItem>bar</listItem> <li>foo</li>
|
|
278
|
-
* <li>bar</li>
|
|
279
|
-
* </ul>
|
|
280
|
-
* ```
|
|
281
|
-
*
|
|
282
|
-
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:remove
|
|
283
|
-
*/
|
|
284
|
-
export const modelViewMergeAfter = (evt, data, conversionApi) => {
|
|
285
|
-
const viewPosition = conversionApi.mapper.toViewPosition(data.position);
|
|
286
|
-
const viewItemPrev = viewPosition.nodeBefore;
|
|
287
|
-
const viewItemNext = viewPosition.nodeAfter;
|
|
288
|
-
// Merge lists if something (remove, move) was done from inside of list.
|
|
289
|
-
// Merging will be done only if both items are view lists of the same type.
|
|
290
|
-
// The check is done inside the helper function.
|
|
291
|
-
mergeViewLists(conversionApi.writer, viewItemPrev, viewItemNext);
|
|
292
|
-
};
|
|
293
|
-
/**
|
|
294
|
-
* A view-to-model converter that converts the `<li>` view elements into the `listItem` model elements.
|
|
295
|
-
*
|
|
296
|
-
* To set correct values of the `listType` and `listIndent` attributes the converter:
|
|
297
|
-
* * checks `<li>`'s parent,
|
|
298
|
-
* * stores and increases the `conversionApi.store.indent` value when `<li>`'s sub-items are converted.
|
|
299
|
-
*
|
|
300
|
-
* @see module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element
|
|
301
|
-
*/
|
|
302
|
-
export const viewModelConverter = (evt, data, conversionApi) => {
|
|
303
|
-
if (conversionApi.consumable.consume(data.viewItem, { name: true })) {
|
|
304
|
-
const writer = conversionApi.writer;
|
|
305
|
-
// 1. Create `listItem` model element.
|
|
306
|
-
const listItem = writer.createElement('listItem');
|
|
307
|
-
// 2. Handle `listItem` model element attributes.
|
|
308
|
-
const indent = getIndent(data.viewItem);
|
|
309
|
-
writer.setAttribute('listIndent', indent, listItem);
|
|
310
|
-
// Set 'bulleted' as default. If this item is pasted into a context,
|
|
311
|
-
const type = data.viewItem.parent && data.viewItem.parent.name == 'ol' ? 'numbered' : 'bulleted';
|
|
312
|
-
writer.setAttribute('listType', type, listItem);
|
|
313
|
-
if (!conversionApi.safeInsert(listItem, data.modelCursor)) {
|
|
314
|
-
return;
|
|
139
|
+
for (const listHead of itemToListHead.values()) {
|
|
140
|
+
itemsToRefresh.push(...collectListItemsToRefresh(listHead, changedItems));
|
|
315
141
|
}
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
142
|
+
for (const item of new Set(itemsToRefresh)) {
|
|
143
|
+
editing.reconvertItem(item);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
function collectListItemsToRefresh(listHead, changedItems) {
|
|
147
|
+
const itemsToRefresh = [];
|
|
148
|
+
const visited = new Set();
|
|
149
|
+
const stack = [];
|
|
150
|
+
for (const { node, previous } of iterateSiblingListBlocks(listHead, 'forward')) {
|
|
151
|
+
if (visited.has(node)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const itemIndent = node.getAttribute('listIndent');
|
|
155
|
+
// Current node is at the lower indent so trim the stack.
|
|
156
|
+
if (previous && itemIndent < previous.getAttribute('listIndent')) {
|
|
157
|
+
stack.length = itemIndent + 1;
|
|
158
|
+
}
|
|
159
|
+
// Update the stack for the current indent level.
|
|
160
|
+
stack[itemIndent] = Object.fromEntries(Array.from(node.getAttributes())
|
|
161
|
+
.filter(([key]) => attributeNames.includes(key)));
|
|
162
|
+
// Find all blocks of the current node.
|
|
163
|
+
const blocks = getListItemBlocks(node, { direction: 'forward' });
|
|
164
|
+
for (const block of blocks) {
|
|
165
|
+
visited.add(block);
|
|
166
|
+
// Check if bogus vs plain paragraph needs refresh.
|
|
167
|
+
if (doesItemBlockRequiresRefresh(block, blocks)) {
|
|
168
|
+
itemsToRefresh.push(block);
|
|
169
|
+
}
|
|
170
|
+
// Check if wrapping with UL, OL, LIs needs refresh.
|
|
171
|
+
else if (doesItemWrappingRequiresRefresh(block, stack, changedItems)) {
|
|
172
|
+
itemsToRefresh.push(block);
|
|
173
|
+
}
|
|
337
174
|
}
|
|
338
175
|
}
|
|
176
|
+
return itemsToRefresh;
|
|
339
177
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
178
|
+
function doesItemBlockRequiresRefresh(item, blocks) {
|
|
179
|
+
const viewElement = editing.mapper.toViewElement(item);
|
|
180
|
+
if (!viewElement) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const needsRefresh = listEditing.fire('checkElement', {
|
|
184
|
+
modelElement: item,
|
|
185
|
+
viewElement
|
|
186
|
+
});
|
|
187
|
+
if (needsRefresh) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
if (!item.is('element', 'paragraph') && !item.is('element', 'listItem')) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
const useBogus = shouldUseBogusParagraph(item, attributeNames, blocks);
|
|
194
|
+
if (useBogus && viewElement.is('element', 'p')) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
else if (!useBogus && viewElement.is('element', 'span')) {
|
|
198
|
+
return true;
|
|
350
199
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
function doesItemWrappingRequiresRefresh(item, stack, changedItems) {
|
|
203
|
+
// Items directly affected by some "change" don't need a refresh, they will be converted by their own changes.
|
|
204
|
+
if (changedItems.has(item)) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
const viewElement = editing.mapper.toViewElement(item);
|
|
208
|
+
let indent = stack.length - 1;
|
|
209
|
+
// Traverse down the stack to the root to verify if all ULs, OLs, and LIs are as expected.
|
|
210
|
+
for (let element = viewElement.parent; !element.is('editableElement'); element = element.parent) {
|
|
211
|
+
const isListItemElement = isListItemView(element);
|
|
212
|
+
const isListElement = isListView(element);
|
|
213
|
+
if (!isListElement && !isListItemElement) {
|
|
214
|
+
continue;
|
|
356
215
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
216
|
+
const eventName = `checkAttributes:${isListItemElement ? 'item' : 'list'}`;
|
|
217
|
+
const needsRefresh = listEditing.fire(eventName, {
|
|
218
|
+
viewElement: element,
|
|
219
|
+
modelAttributes: stack[indent]
|
|
220
|
+
});
|
|
221
|
+
if (needsRefresh) {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
if (isListElement) {
|
|
225
|
+
indent--;
|
|
226
|
+
// Don't need to iterate further if we already know that the item is wrapped appropriately.
|
|
227
|
+
if (indent < 0) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
360
230
|
}
|
|
361
231
|
}
|
|
232
|
+
return true;
|
|
362
233
|
}
|
|
363
|
-
}
|
|
234
|
+
}
|
|
364
235
|
/**
|
|
365
|
-
* Returns
|
|
366
|
-
*
|
|
367
|
-
*
|
|
236
|
+
* Returns the list item downcast converter.
|
|
237
|
+
*
|
|
238
|
+
* @internal
|
|
239
|
+
* @param attributeNames A list of attribute names that should be converted if they are set.
|
|
240
|
+
* @param strategies The strategies.
|
|
241
|
+
* @param model The model.
|
|
368
242
|
*/
|
|
369
|
-
export function
|
|
370
|
-
|
|
371
|
-
|
|
243
|
+
export function listItemDowncastConverter(attributeNames, strategies, model, { dataPipeline } = {}) {
|
|
244
|
+
const consumer = createAttributesConsumer(attributeNames);
|
|
245
|
+
return (evt, data, conversionApi) => {
|
|
246
|
+
const { writer, mapper, consumable } = conversionApi;
|
|
247
|
+
const listItem = data.item;
|
|
248
|
+
if (!attributeNames.includes(data.attributeKey)) {
|
|
372
249
|
return;
|
|
373
250
|
}
|
|
374
|
-
|
|
375
|
-
if (
|
|
376
|
-
|
|
377
|
-
const topmostViewList = viewItem.getAncestors().find(isList);
|
|
378
|
-
const walker = view.createPositionAt(viewItem, 0).getWalker();
|
|
379
|
-
for (const value of walker) {
|
|
380
|
-
if (value.type == 'elementStart' && value.item.is('element', 'li')) {
|
|
381
|
-
data.viewPosition = value.previousPosition;
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
else if (value.type == 'elementEnd' && value.item == topmostViewList) {
|
|
385
|
-
data.viewPosition = value.nextPosition;
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
251
|
+
// Test if attributes on the converted items are not consumed.
|
|
252
|
+
if (!consumer(listItem, consumable)) {
|
|
253
|
+
return;
|
|
389
254
|
}
|
|
255
|
+
// Use positions mapping instead of mapper.toViewElement( listItem ) to find outermost view element.
|
|
256
|
+
// This is for cases when mapping is using inner view element like in the code blocks (pre > code).
|
|
257
|
+
const viewElement = findMappedViewElement(listItem, mapper, model);
|
|
258
|
+
// Remove custom item marker.
|
|
259
|
+
removeCustomMarkerElements(viewElement, writer, mapper);
|
|
260
|
+
// Unwrap element from current list wrappers.
|
|
261
|
+
unwrapListItemBlock(viewElement, writer);
|
|
262
|
+
// Insert custom item marker.
|
|
263
|
+
const viewRange = insertCustomMarkerElements(listItem, viewElement, strategies, writer, { dataPipeline });
|
|
264
|
+
// Then wrap them with the new list wrappers (UL, OL, LI).
|
|
265
|
+
wrapListItemBlock(listItem, viewRange, strategies, writer);
|
|
390
266
|
};
|
|
391
267
|
}
|
|
392
268
|
/**
|
|
393
|
-
*
|
|
394
|
-
* positions between the `<li>` elements that would be incorrectly mapped because of how list items are represented in the model
|
|
395
|
-
* and in the view.
|
|
269
|
+
* Returns the bogus paragraph view element creator. A bogus paragraph is used if a list item contains only a single block or nested list.
|
|
396
270
|
*
|
|
397
|
-
* @
|
|
398
|
-
* @param model
|
|
399
|
-
* @returns Returns a conversion callback.
|
|
271
|
+
* @internal
|
|
272
|
+
* @param attributeNames The list of all model list attributes (including registered strategies).
|
|
400
273
|
*/
|
|
401
|
-
export function
|
|
402
|
-
return (
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
else {
|
|
415
|
-
// Position is at the end of <ul> or <ol>, so there is no <li> after it to be mapped.
|
|
416
|
-
// There is <li> before the position, but we cannot just map it to `listItem` and set model position after it,
|
|
417
|
-
// because that <li> may contain nested items.
|
|
418
|
-
// We will check "model length" of that <li>, in other words - how many `listItem`s are in that <li>.
|
|
419
|
-
const modelNode = mapper.toModelElement(viewPos.nodeBefore);
|
|
420
|
-
const modelLength = mapper.getModelLength(viewPos.nodeBefore);
|
|
421
|
-
// Then we get model position before mapped `listItem` and shift it accordingly.
|
|
422
|
-
data.modelPosition = model.createPositionBefore(modelNode).getShiftedBy(modelLength);
|
|
423
|
-
}
|
|
424
|
-
evt.stop();
|
|
425
|
-
}
|
|
426
|
-
else if (viewParent.name == 'li' &&
|
|
427
|
-
viewPos.nodeBefore &&
|
|
428
|
-
(viewPos.nodeBefore.name == 'ul' || viewPos.nodeBefore.name == 'ol')) {
|
|
429
|
-
// In most cases when view position is in <li> it is in text and this is a correct position.
|
|
430
|
-
// However, if position is after <ul> or <ol> we have to fix it -- because in model <ul>/<ol> are not in the `listItem`.
|
|
431
|
-
const modelNode = mapper.toModelElement(viewParent);
|
|
432
|
-
// Check all <ul>s and <ol>s that are in the <li> but before mapped position.
|
|
433
|
-
// Get model length of those elements and then add it to the offset of `listItem` mapped to the original <li>.
|
|
434
|
-
let modelLength = 1; // Starts from 1 because the original <li> has to be counted in too.
|
|
435
|
-
let viewList = viewPos.nodeBefore;
|
|
436
|
-
while (viewList && isList(viewList)) {
|
|
437
|
-
modelLength += mapper.getModelLength(viewList);
|
|
438
|
-
viewList = viewList.previousSibling;
|
|
439
|
-
}
|
|
440
|
-
data.modelPosition = model.createPositionBefore(modelNode).getShiftedBy(modelLength);
|
|
441
|
-
evt.stop();
|
|
442
|
-
}
|
|
274
|
+
export function bogusParagraphCreator(attributeNames, { dataPipeline } = {}) {
|
|
275
|
+
return (modelElement, { writer }) => {
|
|
276
|
+
// Convert only if a bogus paragraph should be used.
|
|
277
|
+
if (!shouldUseBogusParagraph(modelElement, attributeNames)) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
if (!dataPipeline) {
|
|
281
|
+
return writer.createContainerElement('span', { class: 'ck-list-bogus-paragraph' });
|
|
282
|
+
}
|
|
283
|
+
// Using `<p>` in case there are some markers on it and transparentRendering will render it anyway.
|
|
284
|
+
const viewElement = writer.createContainerElement('p');
|
|
285
|
+
writer.setCustomProperty('dataPipeline:transparentRendering', true, viewElement);
|
|
286
|
+
return viewElement;
|
|
443
287
|
};
|
|
444
288
|
}
|
|
445
289
|
/**
|
|
446
|
-
*
|
|
290
|
+
* Helper for mapping mode to view elements. It's using positions mapping instead of mapper.toViewElement( element )
|
|
291
|
+
* to find outermost view element. This is for cases when mapping is using inner view element like in the code blocks (pre > code).
|
|
447
292
|
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
* <listItem listType="bulleted" listIndent=0>Item 1</listItem>
|
|
453
|
-
* <listItem listType="bulleted" listIndent=1>Item 2</listItem> <--- this is removed.
|
|
454
|
-
* <listItem listType="bulleted" listIndent=2>Item 3</listItem>
|
|
455
|
-
* ```
|
|
456
|
-
*
|
|
457
|
-
* The list structure after the middle element is removed:
|
|
458
|
-
*
|
|
459
|
-
* ```xml
|
|
460
|
-
* <listItem listType="bulleted" listIndent=0>Item 1</listItem>
|
|
461
|
-
* <listItem listType="bulleted" listIndent=2>Item 3</listItem>
|
|
462
|
-
* ```
|
|
463
|
-
*
|
|
464
|
-
* Should become:
|
|
465
|
-
*
|
|
466
|
-
* ```xml
|
|
467
|
-
* <listItem listType="bulleted" listIndent=0>Item 1</listItem>
|
|
468
|
-
* <listItem listType="bulleted" listIndent=1>Item 3</listItem> <--- note that indent got post-fixed.
|
|
469
|
-
* ```
|
|
470
|
-
*
|
|
471
|
-
* @param model The data model.
|
|
472
|
-
* @param writer The writer to do changes with.
|
|
473
|
-
* @returns `true` if any change has been applied, `false` otherwise.
|
|
293
|
+
* @internal
|
|
294
|
+
* @param element The model element.
|
|
295
|
+
* @param mapper The mapper instance.
|
|
296
|
+
* @param model The model.
|
|
474
297
|
*/
|
|
475
|
-
export function
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (item.hasAttribute('listIndent')) {
|
|
488
|
-
writer.removeAttribute('listIndent', item);
|
|
489
|
-
applied = true;
|
|
490
|
-
}
|
|
491
|
-
if (item.hasAttribute('listType')) {
|
|
492
|
-
writer.removeAttribute('listType', item);
|
|
493
|
-
applied = true;
|
|
494
|
-
}
|
|
495
|
-
if (item.hasAttribute('listStyle')) {
|
|
496
|
-
writer.removeAttribute('listStyle', item);
|
|
497
|
-
applied = true;
|
|
498
|
-
}
|
|
499
|
-
if (item.hasAttribute('listReversed')) {
|
|
500
|
-
writer.removeAttribute('listReversed', item);
|
|
501
|
-
applied = true;
|
|
502
|
-
}
|
|
503
|
-
if (item.hasAttribute('listStart')) {
|
|
504
|
-
writer.removeAttribute('listStart', item);
|
|
505
|
-
applied = true;
|
|
506
|
-
}
|
|
507
|
-
for (const innerItem of Array.from(model.createRangeIn(item)).filter(e => e.item.is('element', 'listItem'))) {
|
|
508
|
-
_addListToFix(innerItem.previousPosition);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
const posAfter = entry.position.getShiftedBy(entry.length);
|
|
512
|
-
_addListToFix(posAfter);
|
|
513
|
-
}
|
|
514
|
-
else if (entry.type == 'remove' && entry.name == 'listItem') {
|
|
515
|
-
_addListToFix(entry.position);
|
|
516
|
-
}
|
|
517
|
-
else if (entry.type == 'attribute' && entry.attributeKey == 'listIndent') {
|
|
518
|
-
_addListToFix(entry.range.start);
|
|
519
|
-
}
|
|
520
|
-
else if (entry.type == 'attribute' && entry.attributeKey == 'listType') {
|
|
521
|
-
_addListToFix(entry.range.start);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
for (const listHead of itemToListHead.values()) {
|
|
525
|
-
_fixListIndents(listHead);
|
|
526
|
-
_fixListTypes(listHead);
|
|
298
|
+
export function findMappedViewElement(element, mapper, model) {
|
|
299
|
+
const modelRange = model.createRangeOn(element);
|
|
300
|
+
const viewRange = mapper.toViewRange(modelRange).getTrimmed();
|
|
301
|
+
return viewRange.end.nodeBefore;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Removes a custom marker elements and item wrappers related to that marker.
|
|
305
|
+
*/
|
|
306
|
+
function removeCustomMarkerElements(viewElement, viewWriter, mapper) {
|
|
307
|
+
// Remove item wrapper.
|
|
308
|
+
while (viewElement.parent.is('attributeElement') && viewElement.parent.getCustomProperty('listItemWrapper')) {
|
|
309
|
+
viewWriter.unwrap(viewWriter.createRangeIn(viewElement.parent), viewElement.parent);
|
|
527
310
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
else {
|
|
538
|
-
let listHead = previousNode;
|
|
539
|
-
if (itemToListHead.has(listHead)) {
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
for (
|
|
543
|
-
// Cache previousSibling and reuse for performance reasons. See #6581.
|
|
544
|
-
let previousSibling = listHead.previousSibling; previousSibling && previousSibling.is('element', 'listItem'); previousSibling = listHead.previousSibling) {
|
|
545
|
-
listHead = previousSibling;
|
|
546
|
-
if (itemToListHead.has(listHead)) {
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
itemToListHead.set(previousNode, listHead);
|
|
311
|
+
// Remove custom item markers.
|
|
312
|
+
const viewWalker = viewWriter.createPositionBefore(viewElement).getWalker({ direction: 'backward' });
|
|
313
|
+
const markersToRemove = [];
|
|
314
|
+
for (const { item } of viewWalker) {
|
|
315
|
+
// Walk only over the non-mapped elements between list item blocks.
|
|
316
|
+
if (item.is('element') && mapper.toModelElement(item)) {
|
|
317
|
+
break;
|
|
551
318
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
let maxIndent = 0;
|
|
555
|
-
let fixBy = null;
|
|
556
|
-
while (item && item.is('element', 'listItem')) {
|
|
557
|
-
const itemIndent = item.getAttribute('listIndent');
|
|
558
|
-
if (itemIndent > maxIndent) {
|
|
559
|
-
let newIndent;
|
|
560
|
-
if (fixBy === null) {
|
|
561
|
-
fixBy = itemIndent - maxIndent;
|
|
562
|
-
newIndent = maxIndent;
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
if (fixBy > itemIndent) {
|
|
566
|
-
fixBy = itemIndent;
|
|
567
|
-
}
|
|
568
|
-
newIndent = itemIndent - fixBy;
|
|
569
|
-
}
|
|
570
|
-
writer.setAttribute('listIndent', newIndent, item);
|
|
571
|
-
applied = true;
|
|
572
|
-
}
|
|
573
|
-
else {
|
|
574
|
-
fixBy = null;
|
|
575
|
-
maxIndent = item.getAttribute('listIndent') + 1;
|
|
576
|
-
}
|
|
577
|
-
item = item.nextSibling;
|
|
319
|
+
if (item.is('element') && item.getCustomProperty('listItemMarker')) {
|
|
320
|
+
markersToRemove.push(item);
|
|
578
321
|
}
|
|
579
322
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
let prev = null;
|
|
583
|
-
while (item && item.is('element', 'listItem')) {
|
|
584
|
-
const itemIndent = item.getAttribute('listIndent');
|
|
585
|
-
if (prev && prev.getAttribute('listIndent') > itemIndent) {
|
|
586
|
-
typesStack = typesStack.slice(0, itemIndent + 1);
|
|
587
|
-
}
|
|
588
|
-
if (itemIndent != 0) {
|
|
589
|
-
if (typesStack[itemIndent]) {
|
|
590
|
-
const type = typesStack[itemIndent];
|
|
591
|
-
if (item.getAttribute('listType') != type) {
|
|
592
|
-
writer.setAttribute('listType', type, item);
|
|
593
|
-
applied = true;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
else {
|
|
597
|
-
typesStack[itemIndent] = item.getAttribute('listType');
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
prev = item;
|
|
601
|
-
item = item.nextSibling;
|
|
602
|
-
}
|
|
323
|
+
for (const marker of markersToRemove) {
|
|
324
|
+
viewWriter.remove(marker);
|
|
603
325
|
}
|
|
604
326
|
}
|
|
605
327
|
/**
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
* It fixes indentation of pasted list items so the pasted items match correctly to the context they are pasted into.
|
|
609
|
-
*
|
|
610
|
-
* Example:
|
|
611
|
-
*
|
|
612
|
-
* ```xml
|
|
613
|
-
* <listItem listType="bulleted" listIndent=0>A</listItem>
|
|
614
|
-
* <listItem listType="bulleted" listIndent=1>B^</listItem>
|
|
615
|
-
* // At ^ paste: <listItem listType="bulleted" listIndent=4>X</listItem>
|
|
616
|
-
* // <listItem listType="bulleted" listIndent=5>Y</listItem>
|
|
617
|
-
* <listItem listType="bulleted" listIndent=2>C</listItem>
|
|
618
|
-
* ```
|
|
619
|
-
*
|
|
620
|
-
* Should become:
|
|
621
|
-
*
|
|
622
|
-
* ```xml
|
|
623
|
-
* <listItem listType="bulleted" listIndent=0>A</listItem>
|
|
624
|
-
* <listItem listType="bulleted" listIndent=1>BX</listItem>
|
|
625
|
-
* <listItem listType="bulleted" listIndent=2>Y/listItem>
|
|
626
|
-
* <listItem listType="bulleted" listIndent=2>C</listItem>
|
|
627
|
-
* ```
|
|
328
|
+
* Inserts a custom marker elements and wraps first block of a list item if marker requires it.
|
|
628
329
|
*/
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
//
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
// Note: we also need to handle singular elements because inserting item with indent 0 into 0,1,[],2
|
|
635
|
-
// would create incorrect model.
|
|
636
|
-
let item = content.is('documentFragment') ? content.getChild(0) : content;
|
|
637
|
-
let selection;
|
|
638
|
-
if (!selectable) {
|
|
639
|
-
selection = model.document.selection;
|
|
640
|
-
}
|
|
641
|
-
else {
|
|
642
|
-
selection = model.createSelection(selectable);
|
|
643
|
-
}
|
|
644
|
-
if (item && item.is('element', 'listItem')) {
|
|
645
|
-
// Get a reference list item. Inserted list items will be fixed according to that item.
|
|
646
|
-
const pos = selection.getFirstPosition();
|
|
647
|
-
let refItem = null;
|
|
648
|
-
if (pos.parent.is('element', 'listItem')) {
|
|
649
|
-
refItem = pos.parent;
|
|
650
|
-
}
|
|
651
|
-
else if (pos.nodeBefore && pos.nodeBefore.is('element', 'listItem')) {
|
|
652
|
-
refItem = pos.nodeBefore;
|
|
653
|
-
}
|
|
654
|
-
// If there is `refItem` it means that we do insert list items into an existing list.
|
|
655
|
-
if (refItem) {
|
|
656
|
-
// First list item in `data` has indent equal to 0 (it is a first list item). It should have indent equal
|
|
657
|
-
// to the indent of reference item. We have to fix the first item and all of it's children and following siblings.
|
|
658
|
-
// Indent of all those items has to be adjusted to reference item.
|
|
659
|
-
const indentChange = refItem.getAttribute('listIndent');
|
|
660
|
-
// Fix only if there is anything to fix.
|
|
661
|
-
if (indentChange > 0) {
|
|
662
|
-
// Adjust indent of all "first" list items in inserted data.
|
|
663
|
-
while (item && item.is('element', 'listItem')) {
|
|
664
|
-
item._setAttribute('listIndent', item.getAttribute('listIndent') + indentChange);
|
|
665
|
-
item = item.nextSibling;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
330
|
+
function insertCustomMarkerElements(listItem, viewElement, strategies, writer, { dataPipeline }) {
|
|
331
|
+
let viewRange = writer.createRangeOn(viewElement);
|
|
332
|
+
// Marker can be inserted only before the first block of a list item.
|
|
333
|
+
if (!isFirstBlockOfListItem(listItem)) {
|
|
334
|
+
return viewRange;
|
|
669
335
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
// something split list item).
|
|
692
|
-
//
|
|
693
|
-
// If this is a list, we expect that some `listItem`s and possibly other blocks will be inserted, however `.modelCursor`
|
|
694
|
-
// should be set after last `listItem` (or block). This is why it feels safe to use it as `nextPosition`
|
|
695
|
-
nextPosition = conversionApi.convertItem(child, nextPosition).modelCursor;
|
|
336
|
+
for (const strategy of strategies) {
|
|
337
|
+
if (strategy.scope != 'itemMarker') {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
// Create the custom marker element and inject it before the first block of the list item.
|
|
341
|
+
const markerElement = strategy.createElement(writer, listItem, { dataPipeline });
|
|
342
|
+
if (!markerElement) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
writer.setCustomProperty('listItemMarker', true, markerElement);
|
|
346
|
+
writer.insert(viewRange.start, markerElement);
|
|
347
|
+
viewRange = writer.createRange(writer.createPositionBefore(markerElement), writer.createPositionAfter(viewElement));
|
|
348
|
+
// Wrap the marker and optionally the first block with an attribute element (label for to-do lists).
|
|
349
|
+
if (!strategy.createWrapperElement || !strategy.canWrapElement) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const wrapper = strategy.createWrapperElement(writer, listItem, { dataPipeline });
|
|
353
|
+
writer.setCustomProperty('listItemWrapper', true, wrapper);
|
|
354
|
+
// The whole block can be wrapped...
|
|
355
|
+
if (strategy.canWrapElement(listItem)) {
|
|
356
|
+
viewRange = writer.wrap(viewRange, wrapper);
|
|
696
357
|
}
|
|
697
358
|
else {
|
|
698
|
-
//
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
//
|
|
702
|
-
// <li><p>Foo</p></li>
|
|
703
|
-
//
|
|
704
|
-
// will be converted to:
|
|
705
|
-
//
|
|
706
|
-
// <listItem></listItem><paragraph>Foo</paragraph><listItem></listItem>
|
|
707
|
-
//
|
|
708
|
-
const convertedChild = result.modelRange.start.nodeAfter;
|
|
709
|
-
const wasSplit = convertedChild && convertedChild.is('element') && !schema.checkChild(listItemModel, convertedChild.name);
|
|
710
|
-
if (wasSplit) {
|
|
711
|
-
// As `lastListItem` got split, we need to update it to the second part of the split `listItem` element.
|
|
712
|
-
//
|
|
713
|
-
// `modelCursor` should be set to a position where the conversion should continue. There are multiple possible scenarios
|
|
714
|
-
// that may happen. Usually, `modelCursor` (marked as `#` below) would point to the second list item after conversion:
|
|
715
|
-
//
|
|
716
|
-
// `<li><p>Foo</p></li>` -> `<listItem></listItem><paragraph>Foo</paragraph><listItem>#</listItem>`
|
|
717
|
-
//
|
|
718
|
-
// However, in some cases, like auto-paragraphing, the position is placed at the end of the block element:
|
|
719
|
-
//
|
|
720
|
-
// `<li><div>Foo</div></li>` -> `<listItem></listItem><paragraph>Foo#</paragraph><listItem></listItem>`
|
|
721
|
-
//
|
|
722
|
-
// or after an element if another element broken auto-paragraphed element:
|
|
723
|
-
//
|
|
724
|
-
// `<li><div><h2>Foo</h2></div></li>` -> `<listItem></listItem><heading1>Foo</heading1>#<listItem></listItem>`
|
|
725
|
-
//
|
|
726
|
-
// We need to check for such cases and use proper list item and position based on it.
|
|
727
|
-
//
|
|
728
|
-
if (result.modelCursor.parent.is('element', 'listItem')) {
|
|
729
|
-
// (1).
|
|
730
|
-
listItemModel = result.modelCursor.parent;
|
|
731
|
-
}
|
|
732
|
-
else {
|
|
733
|
-
// (2), (3).
|
|
734
|
-
listItemModel = findNextListItem(result.modelCursor);
|
|
735
|
-
}
|
|
736
|
-
nextPosition = writer.createPositionAfter(listItemModel);
|
|
737
|
-
}
|
|
359
|
+
// ... or only the marker element (if the block is downcasted to heading or block widget).
|
|
360
|
+
viewRange = writer.wrap(writer.createRangeOn(markerElement), wrapper);
|
|
361
|
+
viewRange = writer.createRange(viewRange.start, writer.createPositionAfter(viewElement));
|
|
738
362
|
}
|
|
739
363
|
}
|
|
740
|
-
return
|
|
364
|
+
return viewRange;
|
|
741
365
|
}
|
|
742
366
|
/**
|
|
743
|
-
*
|
|
367
|
+
* Unwraps all ol, ul, and li attribute elements that are wrapping the provided view element.
|
|
744
368
|
*/
|
|
745
|
-
function
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
369
|
+
function unwrapListItemBlock(viewElement, viewWriter) {
|
|
370
|
+
let attributeElement = viewElement.parent;
|
|
371
|
+
while (attributeElement.is('attributeElement') && ['ul', 'ol', 'li'].includes(attributeElement.name)) {
|
|
372
|
+
const parentElement = attributeElement.parent;
|
|
373
|
+
viewWriter.unwrap(viewWriter.createRangeOn(viewElement), attributeElement);
|
|
374
|
+
attributeElement = parentElement;
|
|
375
|
+
}
|
|
752
376
|
}
|
|
753
377
|
/**
|
|
754
|
-
*
|
|
755
|
-
* to other given parameters.
|
|
378
|
+
* Wraps the given list item with appropriate attribute elements for ul, ol, and li.
|
|
756
379
|
*/
|
|
757
|
-
function
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
// This will be the model element which will get nested items (if it has smaller indent) or sibling items (if it has same indent).
|
|
761
|
-
// Keep in mind that such element might not be found, if removed item was the first item.
|
|
762
|
-
const prevModelItem = getSiblingListItem(modelRemoveStartPosition.nodeBefore, {
|
|
763
|
-
sameIndent: true,
|
|
764
|
-
smallerIndent: true,
|
|
765
|
-
listIndent: nextIndent
|
|
766
|
-
});
|
|
767
|
-
const mapper = conversionApi.mapper;
|
|
768
|
-
const viewWriter = conversionApi.writer;
|
|
769
|
-
// Indent of found element or `null` if the element has not been found.
|
|
770
|
-
const prevIndent = prevModelItem ? prevModelItem.getAttribute('listIndent') : null;
|
|
771
|
-
let insertPosition;
|
|
772
|
-
if (!prevModelItem) {
|
|
773
|
-
// If element has not been found, simply insert lists at the position where the removed item was:
|
|
774
|
-
//
|
|
775
|
-
// Lorem ipsum.
|
|
776
|
-
// 1 -------- <--- this is removed, no previous list item, put nested items in place of removed item.
|
|
777
|
-
// 1.1 -------- <--- this is reference indent.
|
|
778
|
-
// 1.1.1 --------
|
|
779
|
-
// 1.1.2 --------
|
|
780
|
-
// 1.2 --------
|
|
781
|
-
//
|
|
782
|
-
// Becomes:
|
|
783
|
-
//
|
|
784
|
-
// Lorem ipsum.
|
|
785
|
-
// 1.1 --------
|
|
786
|
-
// 1.1.1 --------
|
|
787
|
-
// 1.1.2 --------
|
|
788
|
-
// 1.2 --------
|
|
789
|
-
insertPosition = viewRemoveStartPosition;
|
|
790
|
-
}
|
|
791
|
-
else if (prevIndent == nextIndent) {
|
|
792
|
-
// If element has been found and has same indent as reference indent it means that nested items should
|
|
793
|
-
// become siblings of found element:
|
|
794
|
-
//
|
|
795
|
-
// 1 --------
|
|
796
|
-
// 1.1 --------
|
|
797
|
-
// 1.2 -------- <--- this is `prevModelItem`.
|
|
798
|
-
// 2 -------- <--- this is removed, previous list item has indent same as reference indent.
|
|
799
|
-
// 2.1 -------- <--- this is reference indent, this and 2.2 should become siblings of 1.2.
|
|
800
|
-
// 2.2 --------
|
|
801
|
-
//
|
|
802
|
-
// Becomes:
|
|
803
|
-
//
|
|
804
|
-
// 1 --------
|
|
805
|
-
// 1.1 --------
|
|
806
|
-
// 1.2 --------
|
|
807
|
-
// 2.1 --------
|
|
808
|
-
// 2.2 --------
|
|
809
|
-
const prevViewList = mapper.toViewElement(prevModelItem).parent;
|
|
810
|
-
insertPosition = viewWriter.createPositionAfter(prevViewList);
|
|
811
|
-
}
|
|
812
|
-
else {
|
|
813
|
-
// If element has been found and has smaller indent as reference indent it means that nested items
|
|
814
|
-
// should become nested items of found item:
|
|
815
|
-
//
|
|
816
|
-
// 1 -------- <--- this is `prevModelItem`.
|
|
817
|
-
// 1.1 -------- <--- this is removed, previous list item has indent smaller than reference indent.
|
|
818
|
-
// 1.1.1 -------- <--- this is reference indent, this and 1.1.1 should become nested items of 1.
|
|
819
|
-
// 1.1.2 --------
|
|
820
|
-
// 1.2 --------
|
|
821
|
-
//
|
|
822
|
-
// Becomes:
|
|
823
|
-
//
|
|
824
|
-
// 1 --------
|
|
825
|
-
// 1.1.1 --------
|
|
826
|
-
// 1.1.2 --------
|
|
827
|
-
// 1.2 --------
|
|
828
|
-
//
|
|
829
|
-
// Note: in this case 1.1.1 have indent 2 while 1 have indent 0. In model that should not be possible,
|
|
830
|
-
// because following item may have indent bigger only by one. But this is fixed by postfixer.
|
|
831
|
-
const modelPosition = model.createPositionAt(prevModelItem, 'end');
|
|
832
|
-
insertPosition = mapper.toViewPosition(modelPosition);
|
|
380
|
+
function wrapListItemBlock(listItem, viewRange, strategies, writer) {
|
|
381
|
+
if (!listItem.hasAttribute('listIndent')) {
|
|
382
|
+
return;
|
|
833
383
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
384
|
+
const listItemIndent = listItem.getAttribute('listIndent');
|
|
385
|
+
let currentListItem = listItem;
|
|
386
|
+
for (let indent = listItemIndent; indent >= 0; indent--) {
|
|
387
|
+
const listItemViewElement = createListItemElement(writer, indent, currentListItem.getAttribute('listItemId'));
|
|
388
|
+
const listViewElement = createListElement(writer, indent, currentListItem.getAttribute('listType'));
|
|
389
|
+
for (const strategy of strategies) {
|
|
390
|
+
if ((strategy.scope == 'list' || strategy.scope == 'item') &&
|
|
391
|
+
currentListItem.hasAttribute(strategy.attributeName)) {
|
|
392
|
+
strategy.setAttributeOnDowncast(writer, currentListItem.getAttribute(strategy.attributeName), strategy.scope == 'list' ? listViewElement : listItemViewElement);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
viewRange = writer.wrap(viewRange, listItemViewElement);
|
|
396
|
+
viewRange = writer.wrap(viewRange, listViewElement);
|
|
397
|
+
if (indent == 0) {
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
currentListItem = ListWalker.first(currentListItem, { lowerIndent: true });
|
|
401
|
+
// There is no list item with lower indent, this means this is a document fragment containing
|
|
402
|
+
// only a part of nested list (like copy to clipboard) so we don't need to try to wrap it further.
|
|
403
|
+
if (!currentListItem) {
|
|
404
|
+
break;
|
|
842
405
|
}
|
|
843
406
|
}
|
|
844
407
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
408
|
+
// Returns the function that is responsible for consuming attributes that are set on the model node.
|
|
409
|
+
function createAttributesConsumer(attributeNames) {
|
|
410
|
+
return (node, consumable) => {
|
|
411
|
+
const events = [];
|
|
412
|
+
// Collect all set attributes that are triggering conversion.
|
|
413
|
+
for (const attributeName of attributeNames) {
|
|
414
|
+
if (node.hasAttribute(attributeName)) {
|
|
415
|
+
events.push(`attribute:${attributeName}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (!events.every(event => consumable.test(node, event) !== false)) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
events.forEach(event => consumable.consume(node, event));
|
|
422
|
+
return true;
|
|
423
|
+
};
|
|
850
424
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
* |-> OL |-> OL
|
|
861
|
-
* |-> OL |
|
|
862
|
-
* | |-> OL |
|
|
863
|
-
* | |-> OL |
|
|
864
|
-
* | |-> LI (parent LIs: 1) |-> LI (indent: 1)
|
|
865
|
-
* |-> LI (parent LIs: 1) |-> LI (indent: 1)
|
|
866
|
-
*
|
|
867
|
-
* before: fixed list:
|
|
868
|
-
* OL OL
|
|
869
|
-
* |-> OL |
|
|
870
|
-
* |-> OL |
|
|
871
|
-
* |-> OL |
|
|
872
|
-
* |-> LI (parent LIs: 0) |-> LI (indent: 0)
|
|
873
|
-
*
|
|
874
|
-
* before: fixed list:
|
|
875
|
-
* OL OL
|
|
876
|
-
* |-> LI (parent LIs: 0) |-> LI (indent: 0)
|
|
877
|
-
* |-> OL |-> OL
|
|
878
|
-
* |-> LI (parent LIs: 0) |-> LI (indent: 1)
|
|
879
|
-
* ```
|
|
880
|
-
*/
|
|
881
|
-
function getIndent(listItem) {
|
|
882
|
-
let indent = 0;
|
|
883
|
-
let parent = listItem.parent;
|
|
884
|
-
while (parent) {
|
|
885
|
-
// Each LI in the tree will result in an increased indent for HTML compliant lists.
|
|
886
|
-
if (parent.is('element', 'li')) {
|
|
887
|
-
indent++;
|
|
425
|
+
// Whether the given item should be rendered as a bogus paragraph.
|
|
426
|
+
function shouldUseBogusParagraph(item, attributeNames, blocks = getAllListItemBlocks(item)) {
|
|
427
|
+
if (!isListItemBlock(item)) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
for (const attributeKey of item.getAttributeKeys()) {
|
|
431
|
+
// Ignore selection attributes stored on block elements.
|
|
432
|
+
if (attributeKey.startsWith('selection:')) {
|
|
433
|
+
continue;
|
|
888
434
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
// ...because the we might need increase its indent:
|
|
893
|
-
// before: fixed list:
|
|
894
|
-
// OL OL
|
|
895
|
-
// |-> LI (parent LIs: 0) |-> LI (indent: 0)
|
|
896
|
-
// |-> OL |-> OL
|
|
897
|
-
// |-> LI (parent LIs: 0) |-> LI (indent: 1)
|
|
898
|
-
if (previousSibling && previousSibling.is('element', 'li')) {
|
|
899
|
-
indent++;
|
|
900
|
-
}
|
|
435
|
+
// Don't use bogus paragraph if there are attributes from other features.
|
|
436
|
+
if (!attributeNames.includes(attributeKey)) {
|
|
437
|
+
return false;
|
|
901
438
|
}
|
|
902
|
-
parent = parent.parent;
|
|
903
439
|
}
|
|
904
|
-
return
|
|
440
|
+
return blocks.length < 2;
|
|
905
441
|
}
|