@ckeditor/ckeditor5-list 35.3.2 → 36.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/LICENSE.md +1 -1
- package/build/list.js +2 -2
- package/package.json +43 -39
- package/src/documentlist/converters.js +303 -419
- package/src/documentlist/documentlistcommand.js +136 -207
- package/src/documentlist/documentlistediting.js +538 -697
- package/src/documentlist/documentlistindentcommand.js +115 -168
- package/src/documentlist/documentlistmergecommand.js +161 -222
- package/src/documentlist/documentlistsplitcommand.js +59 -103
- package/src/documentlist/documentlistutils.js +41 -0
- package/src/documentlist/utils/listwalker.js +138 -236
- package/src/documentlist/utils/model.js +322 -421
- package/src/documentlist/utils/postfixers.js +98 -118
- package/src/documentlist/utils/view.js +74 -105
- package/src/documentlist.js +13 -19
- package/src/documentlistproperties/converters.js +33 -47
- package/src/documentlistproperties/documentlistpropertiesediting.js +266 -354
- package/src/documentlistproperties/documentlistpropertiesutils.js +44 -0
- package/src/documentlistproperties/documentlistreversedcommand.js +40 -61
- package/src/documentlistproperties/documentliststartcommand.js +42 -61
- package/src/documentlistproperties/documentliststylecommand.js +97 -147
- package/src/documentlistproperties/utils/style.js +27 -47
- package/src/documentlistproperties.js +13 -19
- package/src/index.js +4 -3
- package/src/list/converters.js +772 -929
- package/src/list/indentcommand.js +105 -140
- package/src/list/listcommand.js +262 -315
- package/src/list/listediting.js +142 -200
- package/src/list/listui.js +16 -25
- package/src/list/listutils.js +46 -0
- package/src/list/utils.js +295 -378
- package/src/list.js +13 -44
- package/src/listcommands.js +5 -0
- package/src/listconfig.js +5 -0
- package/src/listproperties/listpropertiesediting.js +656 -801
- package/src/listproperties/listpropertiesui.js +244 -296
- package/src/listproperties/listreversedcommand.js +37 -49
- package/src/listproperties/liststartcommand.js +37 -49
- package/src/listproperties/liststylecommand.js +82 -115
- package/src/listproperties/ui/collapsibleview.js +75 -138
- package/src/listproperties/ui/listpropertiesview.js +289 -414
- package/src/listproperties.js +13 -118
- package/src/liststyle.js +18 -24
- package/src/todolist/checktodolistcommand.js +60 -102
- package/src/todolist/todolistconverters.js +189 -271
- package/src/todolist/todolistediting.js +141 -206
- package/src/todolist/todolistui.js +14 -21
- package/src/todolist.js +13 -19
- package/theme/collapsible.css +1 -1
- package/theme/documentlist.css +1 -1
- package/theme/list.css +40 -0
- package/theme/listproperties.css +1 -1
- package/theme/liststyles.css +1 -37
- package/theme/todolist.css +1 -1
- package/build/list.js.map +0 -1
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2023, 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
|
-
|
|
6
5
|
/**
|
|
7
6
|
* @module list/listproperties/listpropertiesediting
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
8
|
import { Plugin } from 'ckeditor5/src/core';
|
|
11
9
|
import ListEditing from '../list/listediting';
|
|
12
10
|
import ListStyleCommand from './liststylecommand';
|
|
13
11
|
import ListReversedCommand from './listreversedcommand';
|
|
14
12
|
import ListStartCommand from './liststartcommand';
|
|
15
13
|
import { getSiblingListItem, getSiblingNodes } from '../list/utils';
|
|
16
|
-
|
|
17
14
|
const DEFAULT_LIST_TYPE = 'default';
|
|
18
|
-
|
|
19
15
|
/**
|
|
20
16
|
* The engine of the list properties feature.
|
|
21
17
|
*
|
|
@@ -23,815 +19,674 @@ const DEFAULT_LIST_TYPE = 'default';
|
|
|
23
19
|
* allows modifying the list style type.
|
|
24
20
|
*
|
|
25
21
|
* It registers the `'listStyle'`, `'listReversed'` and `'listStart'` commands if they are enabled in the configuration.
|
|
26
|
-
* Read more in {@link module:list/
|
|
27
|
-
*
|
|
28
|
-
* @extends module:core/plugin~Plugin
|
|
22
|
+
* Read more in {@link module:list/listconfig~ListPropertiesConfig}.
|
|
29
23
|
*/
|
|
30
24
|
export default class ListPropertiesEditing extends Plugin {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
} );
|
|
217
|
-
|
|
218
|
-
// If the selection ends in a non-list element, there are no <listItem>s that would require adjustments.
|
|
219
|
-
// See: #8642.
|
|
220
|
-
if ( !secondListMostOuterItem ) {
|
|
221
|
-
firstMostOuterItem = null;
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const items = [
|
|
226
|
-
secondListMostOuterItem,
|
|
227
|
-
...getSiblingNodes( writer.createPositionAt( secondListMostOuterItem, 0 ), 'forward' )
|
|
228
|
-
];
|
|
229
|
-
|
|
230
|
-
for ( const listItem of items ) {
|
|
231
|
-
for ( const strategy of attributeStrategies ) {
|
|
232
|
-
if ( strategy.appliesToListItem( listItem ) ) {
|
|
233
|
-
const attributeName = strategy.attributeName;
|
|
234
|
-
const value = firstMostOuterItem.getAttribute( attributeName );
|
|
235
|
-
|
|
236
|
-
writer.setAttribute( attributeName, value, listItem );
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
} );
|
|
241
|
-
|
|
242
|
-
firstMostOuterItem = null;
|
|
243
|
-
}, { priority: 'low' } );
|
|
244
|
-
}
|
|
25
|
+
/**
|
|
26
|
+
* @inheritDoc
|
|
27
|
+
*/
|
|
28
|
+
static get requires() {
|
|
29
|
+
return [ListEditing];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @inheritDoc
|
|
33
|
+
*/
|
|
34
|
+
static get pluginName() {
|
|
35
|
+
return 'ListPropertiesEditing';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* @inheritDoc
|
|
39
|
+
*/
|
|
40
|
+
constructor(editor) {
|
|
41
|
+
super(editor);
|
|
42
|
+
editor.config.define('list', {
|
|
43
|
+
properties: {
|
|
44
|
+
styles: true,
|
|
45
|
+
startIndex: false,
|
|
46
|
+
reversed: false
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* @inheritDoc
|
|
52
|
+
*/
|
|
53
|
+
init() {
|
|
54
|
+
const editor = this.editor;
|
|
55
|
+
const model = editor.model;
|
|
56
|
+
const enabledProperties = editor.config.get('list.properties');
|
|
57
|
+
const strategies = createAttributeStrategies(enabledProperties);
|
|
58
|
+
// Extend schema.
|
|
59
|
+
model.schema.extend('listItem', {
|
|
60
|
+
allowAttributes: strategies.map(s => s.attributeName)
|
|
61
|
+
});
|
|
62
|
+
for (const strategy of strategies) {
|
|
63
|
+
strategy.addCommand(editor);
|
|
64
|
+
}
|
|
65
|
+
// Fix list attributes when modifying their nesting levels (the `listIndent` attribute).
|
|
66
|
+
this.listenTo(editor.commands.get('indentList'), '_executeCleanup', fixListAfterIndentListCommand(editor, strategies));
|
|
67
|
+
this.listenTo(editor.commands.get('outdentList'), '_executeCleanup', fixListAfterOutdentListCommand(editor, strategies));
|
|
68
|
+
this.listenTo(editor.commands.get('bulletedList'), '_executeCleanup', restoreDefaultListStyle(editor));
|
|
69
|
+
this.listenTo(editor.commands.get('numberedList'), '_executeCleanup', restoreDefaultListStyle(editor));
|
|
70
|
+
// Register a post-fixer that ensures that the attributes is specified in each `listItem` element.
|
|
71
|
+
model.document.registerPostFixer(fixListAttributesOnListItemElements(editor, strategies));
|
|
72
|
+
// Set up conversion.
|
|
73
|
+
editor.conversion.for('upcast').add(upcastListItemAttributes(strategies));
|
|
74
|
+
editor.conversion.for('downcast').add(downcastListItemAttributes(strategies));
|
|
75
|
+
// Handle merging two separated lists into the single one.
|
|
76
|
+
this._mergeListAttributesWhileMergingLists(strategies);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* @inheritDoc
|
|
80
|
+
*/
|
|
81
|
+
afterInit() {
|
|
82
|
+
const editor = this.editor;
|
|
83
|
+
// Enable post-fixer that removes the attributes from to-do list items only if the "TodoList" plugin is on.
|
|
84
|
+
// We need to registry the hook here since the `TodoList` plugin can be added after the `ListPropertiesEditing`.
|
|
85
|
+
if (editor.commands.get('todoList')) {
|
|
86
|
+
editor.model.document.registerPostFixer(removeListItemAttributesFromTodoList(editor));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Starts listening to {@link module:engine/model/model~Model#deleteContent} and checks whether two lists will be merged into a single
|
|
91
|
+
* one after deleting the content.
|
|
92
|
+
*
|
|
93
|
+
* The purpose of this action is to adjust the `listStyle`, `listReversed` and `listStart` values
|
|
94
|
+
* for the list that was merged.
|
|
95
|
+
*
|
|
96
|
+
* Consider the following model's content:
|
|
97
|
+
*
|
|
98
|
+
* ```xml
|
|
99
|
+
* <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
|
|
100
|
+
* <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 2</listItem>
|
|
101
|
+
* <paragraph>[A paragraph.]</paragraph>
|
|
102
|
+
* <listItem listIndent="0" listType="bulleted" listStyle="circle">UL List item 1</listItem>
|
|
103
|
+
* <listItem listIndent="0" listType="bulleted" listStyle="circle">UL List item 2</listItem>
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* After removing the paragraph element, the second list will be merged into the first one.
|
|
107
|
+
* We want to inherit the `listStyle` attribute for the second list from the first one.
|
|
108
|
+
*
|
|
109
|
+
* ```xml
|
|
110
|
+
* <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
|
|
111
|
+
* <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 2</listItem>
|
|
112
|
+
* <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
|
|
113
|
+
* <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 2</listItem>
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* See https://github.com/ckeditor/ckeditor5/issues/7879.
|
|
117
|
+
*
|
|
118
|
+
* @param attributeStrategies Strategies for the enabled attributes.
|
|
119
|
+
*/
|
|
120
|
+
_mergeListAttributesWhileMergingLists(attributeStrategies) {
|
|
121
|
+
const editor = this.editor;
|
|
122
|
+
const model = editor.model;
|
|
123
|
+
// First the outer-most`listItem` in the first list reference.
|
|
124
|
+
// If found, the lists should be merged and this `listItem` provides the attributes
|
|
125
|
+
// and it is also a starting point when searching for items in the second list.
|
|
126
|
+
let firstMostOuterItem;
|
|
127
|
+
// Check whether the removed content is between two lists.
|
|
128
|
+
this.listenTo(model, 'deleteContent', (evt, [selection]) => {
|
|
129
|
+
const firstPosition = selection.getFirstPosition();
|
|
130
|
+
const lastPosition = selection.getLastPosition();
|
|
131
|
+
// Typing or removing content in a single item. Aborting.
|
|
132
|
+
if (firstPosition.parent === lastPosition.parent) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// An element before the content that will be removed is not a list.
|
|
136
|
+
if (!firstPosition.parent.is('element', 'listItem')) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const nextSibling = lastPosition.parent.nextSibling;
|
|
140
|
+
// An element after the content that will be removed is not a list.
|
|
141
|
+
if (!nextSibling || !nextSibling.is('element', 'listItem')) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Find the outermost list item based on the `listIndent` attribute. We can't assume that `listIndent=0`
|
|
145
|
+
// because the selection can be hooked in nested lists.
|
|
146
|
+
//
|
|
147
|
+
// <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
|
|
148
|
+
// <listItem listIndent="1" listType="bulleted" listStyle="square">UL List [item 1.1</listItem>
|
|
149
|
+
// <listItem listIndent="0" listType="bulleted" listStyle="circle">[]UL List item 1.</listItem>
|
|
150
|
+
// <listItem listIndent="1" listType="bulleted" listStyle="circle">UL List ]item 1.1</listItem>
|
|
151
|
+
//
|
|
152
|
+
// After deleting the content, we would like to inherit the "square" attribute for the last element:
|
|
153
|
+
//
|
|
154
|
+
// <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
|
|
155
|
+
// <listItem listIndent="1" listType="bulleted" listStyle="square">UL List []item 1.1</listItem>
|
|
156
|
+
const mostOuterItemList = getSiblingListItem(firstPosition.parent, {
|
|
157
|
+
sameIndent: true,
|
|
158
|
+
listIndent: nextSibling.getAttribute('listIndent')
|
|
159
|
+
});
|
|
160
|
+
// The outermost list item may not exist while removing elements between lists with different value
|
|
161
|
+
// of the `listIndent` attribute. In such a case we don't want to update anything. See: #8073.
|
|
162
|
+
if (!mostOuterItemList) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (mostOuterItemList.getAttribute('listType') === nextSibling.getAttribute('listType')) {
|
|
166
|
+
firstMostOuterItem = mostOuterItemList;
|
|
167
|
+
}
|
|
168
|
+
}, { priority: 'high' });
|
|
169
|
+
// If so, update the `listStyle` attribute for the second list.
|
|
170
|
+
this.listenTo(model, 'deleteContent', () => {
|
|
171
|
+
if (!firstMostOuterItem) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
model.change(writer => {
|
|
175
|
+
// Find the first most-outer item list in the merged list.
|
|
176
|
+
// A case when the first list item in the second list was merged into the last item in the first list.
|
|
177
|
+
//
|
|
178
|
+
// <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
|
|
179
|
+
// <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 2</listItem>
|
|
180
|
+
// <listItem listIndent="0" listType="bulleted" listStyle="circle">[]UL List item 1</listItem>
|
|
181
|
+
// <listItem listIndent="0" listType="bulleted" listStyle="circle">UL List item 2</listItem>
|
|
182
|
+
const secondListMostOuterItem = getSiblingListItem(firstMostOuterItem.nextSibling, {
|
|
183
|
+
sameIndent: true,
|
|
184
|
+
listIndent: firstMostOuterItem.getAttribute('listIndent'),
|
|
185
|
+
direction: 'forward'
|
|
186
|
+
});
|
|
187
|
+
// If the selection ends in a non-list element, there are no <listItem>s that would require adjustments.
|
|
188
|
+
// See: #8642.
|
|
189
|
+
if (!secondListMostOuterItem) {
|
|
190
|
+
firstMostOuterItem = null;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const items = [
|
|
194
|
+
secondListMostOuterItem,
|
|
195
|
+
...getSiblingNodes(writer.createPositionAt(secondListMostOuterItem, 0), 'forward')
|
|
196
|
+
];
|
|
197
|
+
for (const listItem of items) {
|
|
198
|
+
for (const strategy of attributeStrategies) {
|
|
199
|
+
if (strategy.appliesToListItem(listItem)) {
|
|
200
|
+
const attributeName = strategy.attributeName;
|
|
201
|
+
const value = firstMostOuterItem.getAttribute(attributeName);
|
|
202
|
+
writer.setAttribute(attributeName, value, listItem);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
firstMostOuterItem = null;
|
|
208
|
+
}, { priority: 'low' });
|
|
209
|
+
}
|
|
245
210
|
}
|
|
246
|
-
|
|
247
211
|
/**
|
|
248
|
-
*
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if ( enabledProperties.startIndex ) {
|
|
325
|
-
strategies.push( {
|
|
326
|
-
attributeName: 'listStart',
|
|
327
|
-
defaultValue: 1,
|
|
328
|
-
|
|
329
|
-
addCommand( editor ) {
|
|
330
|
-
editor.commands.add( 'listStart', new ListStartCommand( editor ) );
|
|
331
|
-
},
|
|
332
|
-
|
|
333
|
-
appliesToListItem( item ) {
|
|
334
|
-
return item.getAttribute( 'listType' ) == 'numbered';
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
setAttributeOnDowncast( writer, listStart, element ) {
|
|
338
|
-
if ( listStart != 1 ) {
|
|
339
|
-
writer.setAttribute( 'start', listStart, element );
|
|
340
|
-
} else {
|
|
341
|
-
writer.removeAttribute( 'start', element );
|
|
342
|
-
}
|
|
343
|
-
},
|
|
344
|
-
|
|
345
|
-
getAttributeOnUpcast( listParent ) {
|
|
346
|
-
return listParent.getAttribute( 'start' ) || 1;
|
|
347
|
-
}
|
|
348
|
-
} );
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return strategies;
|
|
212
|
+
* Creates an array of strategies for dealing with enabled listItem attributes.
|
|
213
|
+
*/
|
|
214
|
+
function createAttributeStrategies(enabledProperties) {
|
|
215
|
+
const strategies = [];
|
|
216
|
+
if (enabledProperties.styles) {
|
|
217
|
+
strategies.push({
|
|
218
|
+
attributeName: 'listStyle',
|
|
219
|
+
defaultValue: DEFAULT_LIST_TYPE,
|
|
220
|
+
addCommand(editor) {
|
|
221
|
+
editor.commands.add('listStyle', new ListStyleCommand(editor, DEFAULT_LIST_TYPE));
|
|
222
|
+
},
|
|
223
|
+
appliesToListItem() {
|
|
224
|
+
return true;
|
|
225
|
+
},
|
|
226
|
+
setAttributeOnDowncast(writer, listStyle, element) {
|
|
227
|
+
if (listStyle && listStyle !== DEFAULT_LIST_TYPE) {
|
|
228
|
+
writer.setStyle('list-style-type', listStyle, element);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
writer.removeStyle('list-style-type', element);
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
getAttributeOnUpcast(listParent) {
|
|
235
|
+
return listParent.getStyle('list-style-type') || DEFAULT_LIST_TYPE;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (enabledProperties.reversed) {
|
|
240
|
+
strategies.push({
|
|
241
|
+
attributeName: 'listReversed',
|
|
242
|
+
defaultValue: false,
|
|
243
|
+
addCommand(editor) {
|
|
244
|
+
editor.commands.add('listReversed', new ListReversedCommand(editor));
|
|
245
|
+
},
|
|
246
|
+
appliesToListItem(item) {
|
|
247
|
+
return item.getAttribute('listType') == 'numbered';
|
|
248
|
+
},
|
|
249
|
+
setAttributeOnDowncast(writer, listReversed, element) {
|
|
250
|
+
if (listReversed) {
|
|
251
|
+
writer.setAttribute('reversed', 'reversed', element);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
writer.removeAttribute('reversed', element);
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
getAttributeOnUpcast(listParent) {
|
|
258
|
+
return listParent.hasAttribute('reversed');
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (enabledProperties.startIndex) {
|
|
263
|
+
strategies.push({
|
|
264
|
+
attributeName: 'listStart',
|
|
265
|
+
defaultValue: 1,
|
|
266
|
+
addCommand(editor) {
|
|
267
|
+
editor.commands.add('listStart', new ListStartCommand(editor));
|
|
268
|
+
},
|
|
269
|
+
appliesToListItem(item) {
|
|
270
|
+
return item.getAttribute('listType') == 'numbered';
|
|
271
|
+
},
|
|
272
|
+
setAttributeOnDowncast(writer, listStart, element) {
|
|
273
|
+
if (listStart == 0 || listStart > 1) {
|
|
274
|
+
writer.setAttribute('start', listStart, element);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
writer.removeAttribute('start', element);
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
getAttributeOnUpcast(listParent) {
|
|
281
|
+
const startAttributeValue = listParent.getAttribute('start');
|
|
282
|
+
return startAttributeValue >= 0 ? startAttributeValue : 1;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return strategies;
|
|
352
287
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
}, { priority: 'low' } );
|
|
373
|
-
};
|
|
288
|
+
/**
|
|
289
|
+
* Returns a converter consumes the `style`, `reversed` and `start` attribute.
|
|
290
|
+
* In `style` it searches for the `list-style-type` definition.
|
|
291
|
+
* If not found, the `"default"` value will be used.
|
|
292
|
+
*/
|
|
293
|
+
function upcastListItemAttributes(attributeStrategies) {
|
|
294
|
+
return (dispatcher) => {
|
|
295
|
+
dispatcher.on('element:li', (evt, data, conversionApi) => {
|
|
296
|
+
const listParent = data.viewItem.parent;
|
|
297
|
+
const listItem = data.modelRange.start.nodeAfter || data.modelRange.end.nodeBefore;
|
|
298
|
+
for (const strategy of attributeStrategies) {
|
|
299
|
+
if (strategy.appliesToListItem(listItem)) {
|
|
300
|
+
const listStyle = strategy.getAttributeOnUpcast(listParent);
|
|
301
|
+
conversionApi.writer.setAttribute(strategy.attributeName, listStyle, listItem);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}, { priority: 'low' });
|
|
305
|
+
};
|
|
374
306
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
function areRepresentingSameList( listItem1, listItem2 ) {
|
|
411
|
-
return listItem2 &&
|
|
412
|
-
listItem1.getAttribute( 'listType' ) === listItem2.getAttribute( 'listType' ) &&
|
|
413
|
-
listItem1.getAttribute( 'listIndent' ) === listItem2.getAttribute( 'listIndent' ) &&
|
|
414
|
-
listItem1.getAttribute( 'listStyle' ) === listItem2.getAttribute( 'listStyle' ) &&
|
|
415
|
-
listItem1.getAttribute( 'listReversed' ) === listItem2.getAttribute( 'listReversed' ) &&
|
|
416
|
-
listItem1.getAttribute( 'listStart' ) === listItem2.getAttribute( 'listStart' );
|
|
417
|
-
}
|
|
307
|
+
/**
|
|
308
|
+
* Returns a converter that adds `reversed`, `start` attributes and adds `list-style-type` definition as a value for the `style` attribute.
|
|
309
|
+
* The `"default"` values are removed and not present in the view/data.
|
|
310
|
+
*/
|
|
311
|
+
function downcastListItemAttributes(attributeStrategies) {
|
|
312
|
+
return (dispatcher) => {
|
|
313
|
+
for (const strategy of attributeStrategies) {
|
|
314
|
+
dispatcher.on(`attribute:${strategy.attributeName}:listItem`, (evt, data, conversionApi) => {
|
|
315
|
+
const viewWriter = conversionApi.writer;
|
|
316
|
+
const currentElement = data.item;
|
|
317
|
+
const previousElement = getSiblingListItem(currentElement.previousSibling, {
|
|
318
|
+
sameIndent: true,
|
|
319
|
+
listIndent: currentElement.getAttribute('listIndent'),
|
|
320
|
+
direction: 'backward'
|
|
321
|
+
});
|
|
322
|
+
const viewItem = conversionApi.mapper.toViewElement(currentElement);
|
|
323
|
+
// A case when elements represent different lists. We need to separate their container.
|
|
324
|
+
if (!areRepresentingSameList(currentElement, previousElement)) {
|
|
325
|
+
viewWriter.breakContainer(viewWriter.createPositionBefore(viewItem));
|
|
326
|
+
}
|
|
327
|
+
strategy.setAttributeOnDowncast(viewWriter, data.attributeNewValue, viewItem.parent);
|
|
328
|
+
}, { priority: 'low' });
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
332
|
+
* Checks whether specified list items belong to the same list.
|
|
333
|
+
*/
|
|
334
|
+
function areRepresentingSameList(listItem1, listItem2) {
|
|
335
|
+
return listItem2 &&
|
|
336
|
+
listItem1.getAttribute('listType') === listItem2.getAttribute('listType') &&
|
|
337
|
+
listItem1.getAttribute('listIndent') === listItem2.getAttribute('listIndent') &&
|
|
338
|
+
listItem1.getAttribute('listStyle') === listItem2.getAttribute('listStyle') &&
|
|
339
|
+
listItem1.getAttribute('listReversed') === listItem2.getAttribute('listReversed') &&
|
|
340
|
+
listItem1.getAttribute('listStart') === listItem2.getAttribute('listStart');
|
|
341
|
+
}
|
|
418
342
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
previousSibling.getAttribute( strategy.attributeName );
|
|
464
|
-
|
|
465
|
-
writer.setAttribute( strategy.attributeName, valueToSet, item );
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
} );
|
|
470
|
-
};
|
|
343
|
+
/**
|
|
344
|
+
* When indenting list, nested list should clear its value for the attributes or inherit from nested lists.
|
|
345
|
+
*
|
|
346
|
+
* ■ List item 1.
|
|
347
|
+
* ■ List item 2.[]
|
|
348
|
+
* ■ List item 3.
|
|
349
|
+
* editor.execute( 'indentList' );
|
|
350
|
+
*
|
|
351
|
+
* ■ List item 1.
|
|
352
|
+
* ○ List item 2.[]
|
|
353
|
+
* ■ List item 3.
|
|
354
|
+
*/
|
|
355
|
+
function fixListAfterIndentListCommand(editor, attributeStrategies) {
|
|
356
|
+
return (evt, changedItems) => {
|
|
357
|
+
const root = changedItems[0];
|
|
358
|
+
const rootIndent = root.getAttribute('listIndent');
|
|
359
|
+
const itemsToUpdate = changedItems.filter(item => item.getAttribute('listIndent') === rootIndent);
|
|
360
|
+
// A case where a few list items are indented must be checked separately
|
|
361
|
+
// since `getSiblingListItem()` returns the first changed element.
|
|
362
|
+
// ■ List item 1.
|
|
363
|
+
// ○ [List item 2.
|
|
364
|
+
// ○ List item 3.]
|
|
365
|
+
// ■ List item 4.
|
|
366
|
+
//
|
|
367
|
+
// List items: `2` and `3` should be adjusted.
|
|
368
|
+
let previousSibling = null;
|
|
369
|
+
if (root.previousSibling.getAttribute('listIndent') + 1 !== rootIndent) {
|
|
370
|
+
previousSibling = getSiblingListItem(root.previousSibling, {
|
|
371
|
+
sameIndent: true, direction: 'backward', listIndent: rootIndent
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
editor.model.change(writer => {
|
|
375
|
+
for (const item of itemsToUpdate) {
|
|
376
|
+
for (const strategy of attributeStrategies) {
|
|
377
|
+
if (strategy.appliesToListItem(item)) {
|
|
378
|
+
const valueToSet = previousSibling == null ?
|
|
379
|
+
strategy.defaultValue :
|
|
380
|
+
previousSibling.getAttribute(strategy.attributeName);
|
|
381
|
+
writer.setAttribute(strategy.attributeName, valueToSet, item);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
};
|
|
471
387
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
for ( const strategy of attributeStrategies ) {
|
|
549
|
-
if ( strategy.appliesToListItem( item ) ) {
|
|
550
|
-
const attributeName = strategy.attributeName;
|
|
551
|
-
const valueToSet = listItem.getAttribute( attributeName );
|
|
552
|
-
|
|
553
|
-
writer.setAttribute( attributeName, valueToSet, item );
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
} );
|
|
558
|
-
};
|
|
388
|
+
/**
|
|
389
|
+
* When outdenting a list, a nested list should copy attribute values
|
|
390
|
+
* from the previous sibling list item including the same value for the `listIndent` value.
|
|
391
|
+
*
|
|
392
|
+
* ■ List item 1.
|
|
393
|
+
* ○ List item 2.[]
|
|
394
|
+
* ■ List item 3.
|
|
395
|
+
*
|
|
396
|
+
* editor.execute( 'outdentList' );
|
|
397
|
+
*
|
|
398
|
+
* ■ List item 1.
|
|
399
|
+
* ■ List item 2.[]
|
|
400
|
+
* ■ List item 3.
|
|
401
|
+
*/
|
|
402
|
+
function fixListAfterOutdentListCommand(editor, attributeStrategies) {
|
|
403
|
+
return (evt, changedItems) => {
|
|
404
|
+
changedItems = changedItems.reverse().filter(item => item.is('element', 'listItem'));
|
|
405
|
+
if (!changedItems.length) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const indent = changedItems[0].getAttribute('listIndent');
|
|
409
|
+
const listType = changedItems[0].getAttribute('listType');
|
|
410
|
+
let listItem = changedItems[0].previousSibling;
|
|
411
|
+
// ■ List item 1.
|
|
412
|
+
// ○ List item 2.
|
|
413
|
+
// ○ List item 3.[]
|
|
414
|
+
// ■ List item 4.
|
|
415
|
+
//
|
|
416
|
+
// After outdenting a list, `List item 3` should inherit the `listStyle` attribute from `List item 1`.
|
|
417
|
+
//
|
|
418
|
+
// ■ List item 1.
|
|
419
|
+
// ○ List item 2.
|
|
420
|
+
// ■ List item 3.[]
|
|
421
|
+
// ■ List item 4.
|
|
422
|
+
if (listItem.is('element', 'listItem')) {
|
|
423
|
+
while (listItem.getAttribute('listIndent') !== indent) {
|
|
424
|
+
listItem = listItem.previousSibling;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
listItem = null;
|
|
429
|
+
}
|
|
430
|
+
// Outdenting such a list should restore values based on `List item 4`.
|
|
431
|
+
// ■ List item 1.[]
|
|
432
|
+
// ○ List item 2.
|
|
433
|
+
// ○ List item 3.
|
|
434
|
+
// ■ List item 4.
|
|
435
|
+
if (!listItem) {
|
|
436
|
+
listItem = changedItems[changedItems.length - 1].nextSibling;
|
|
437
|
+
}
|
|
438
|
+
// And such a list should not modify anything.
|
|
439
|
+
// However, `listItem` can indicate a node below the list. Be sure that we have the `listItem` element.
|
|
440
|
+
// ■ List item 1.[]
|
|
441
|
+
// ○ List item 2.
|
|
442
|
+
// ○ List item 3.
|
|
443
|
+
// <paragraph>The later if check.</paragraph>
|
|
444
|
+
if (!listItem || !listItem.is('element', 'listItem')) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
// Do not modify the list if found `listItem` represents other type of list than outdented list items.
|
|
448
|
+
if (listItem.getAttribute('listType') !== listType) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
editor.model.change(writer => {
|
|
452
|
+
const itemsToUpdate = changedItems.filter(item => item.getAttribute('listIndent') === indent);
|
|
453
|
+
for (const item of itemsToUpdate) {
|
|
454
|
+
for (const strategy of attributeStrategies) {
|
|
455
|
+
if (strategy.appliesToListItem(item)) {
|
|
456
|
+
const attributeName = strategy.attributeName;
|
|
457
|
+
const valueToSet = listItem.getAttribute(attributeName);
|
|
458
|
+
writer.setAttribute(attributeName, valueToSet, item);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
};
|
|
559
464
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
// ○ First. // [listStyle="circle", listType="bulleted"]
|
|
680
|
-
// ○ Second // [listStyle="circle", listType="bulleted"]
|
|
681
|
-
const previousSibling = item.previousSibling;
|
|
682
|
-
|
|
683
|
-
if ( shouldInheritListTypeFromPreviousItem( previousSibling, item, strategy.attributeName ) ) {
|
|
684
|
-
writer.setAttribute( attributeName, previousSibling.getAttribute( attributeName ), item );
|
|
685
|
-
|
|
686
|
-
wasFixed = true;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
return wasFixed;
|
|
693
|
-
};
|
|
465
|
+
/**
|
|
466
|
+
* Each `listItem` element must have specified the `listStyle`, `listReversed` and `listStart` attributes
|
|
467
|
+
* if they are enabled and supported by its `listType`.
|
|
468
|
+
* This post-fixer checks whether inserted elements `listItem` elements should inherit the attribute values from
|
|
469
|
+
* their sibling nodes or should use the default values.
|
|
470
|
+
*
|
|
471
|
+
* Paragraph[]
|
|
472
|
+
* ■ List item 1. // [listStyle="square", listType="bulleted"]
|
|
473
|
+
* ■ List item 2. // ...
|
|
474
|
+
* ■ List item 3. // ...
|
|
475
|
+
*
|
|
476
|
+
* editor.execute( 'bulletedList' )
|
|
477
|
+
*
|
|
478
|
+
* ■ Paragraph[] // [listStyle="square", listType="bulleted"]
|
|
479
|
+
* ■ List item 1. // [listStyle="square", listType="bulleted"]
|
|
480
|
+
* ■ List item 2.
|
|
481
|
+
* ■ List item 3.
|
|
482
|
+
*
|
|
483
|
+
* It also covers a such change:
|
|
484
|
+
*
|
|
485
|
+
* [Paragraph 1
|
|
486
|
+
* Paragraph 2]
|
|
487
|
+
* ■ List item 1. // [listStyle="square", listType="bulleted"]
|
|
488
|
+
* ■ List item 2. // ...
|
|
489
|
+
* ■ List item 3. // ...
|
|
490
|
+
*
|
|
491
|
+
* editor.execute( 'numberedList' )
|
|
492
|
+
*
|
|
493
|
+
* 1. [Paragraph 1 // [listStyle="default", listType="numbered"]
|
|
494
|
+
* 2. Paragraph 2] // [listStyle="default", listType="numbered"]
|
|
495
|
+
* ■ List item 1. // [listStyle="square", listType="bulleted"]
|
|
496
|
+
* ■ List item 2. // ...
|
|
497
|
+
* ■ List item 3. // ...
|
|
498
|
+
*/
|
|
499
|
+
function fixListAttributesOnListItemElements(editor, attributeStrategies) {
|
|
500
|
+
return (writer) => {
|
|
501
|
+
let wasFixed = false;
|
|
502
|
+
const insertedListItems = getChangedListItems(editor.model.document.differ.getChanges())
|
|
503
|
+
.filter(item => {
|
|
504
|
+
// Don't touch todo lists. They are handled in another post-fixer.
|
|
505
|
+
return item.getAttribute('listType') !== 'todo';
|
|
506
|
+
});
|
|
507
|
+
if (!insertedListItems.length) {
|
|
508
|
+
return wasFixed;
|
|
509
|
+
}
|
|
510
|
+
// Check whether the last inserted element is next to the `listItem` element.
|
|
511
|
+
//
|
|
512
|
+
// ■ Paragraph[] // <-- The inserted item.
|
|
513
|
+
// ■ List item 1.
|
|
514
|
+
let existingListItem = insertedListItems[insertedListItems.length - 1].nextSibling;
|
|
515
|
+
// If it doesn't, maybe the `listItem` was inserted at the end of the list.
|
|
516
|
+
//
|
|
517
|
+
// ■ List item 1.
|
|
518
|
+
// ■ Paragraph[] // <-- The inserted item.
|
|
519
|
+
if (!existingListItem || !existingListItem.is('element', 'listItem')) {
|
|
520
|
+
existingListItem = insertedListItems[0].previousSibling;
|
|
521
|
+
if (existingListItem) {
|
|
522
|
+
const indent = insertedListItems[0].getAttribute('listIndent');
|
|
523
|
+
// But we need to find a `listItem` with the `listIndent=0` attribute.
|
|
524
|
+
// If doesn't, maybe the `listItem` was inserted at the end of the list.
|
|
525
|
+
//
|
|
526
|
+
// ■ List item 1.
|
|
527
|
+
// ○ List item 2.
|
|
528
|
+
// ■ Paragraph[] // <-- The inserted item.
|
|
529
|
+
while (existingListItem.is('element', 'listItem') && existingListItem.getAttribute('listIndent') !== indent) {
|
|
530
|
+
existingListItem = existingListItem.previousSibling;
|
|
531
|
+
// If the item does not exist, most probably there is no other content in the editor. See: #8072.
|
|
532
|
+
if (!existingListItem) {
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
for (const strategy of attributeStrategies) {
|
|
539
|
+
const attributeName = strategy.attributeName;
|
|
540
|
+
for (const item of insertedListItems) {
|
|
541
|
+
if (!strategy.appliesToListItem(item)) {
|
|
542
|
+
writer.removeAttribute(attributeName, item);
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (!item.hasAttribute(attributeName)) {
|
|
546
|
+
if (shouldInheritListType(existingListItem, item, strategy)) {
|
|
547
|
+
writer.setAttribute(attributeName, existingListItem.getAttribute(attributeName), item);
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
writer.setAttribute(attributeName, strategy.defaultValue, item);
|
|
551
|
+
}
|
|
552
|
+
wasFixed = true;
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
// Adjust the `listStyle`, `listReversed` and `listStart`
|
|
556
|
+
// attributes for inserted (pasted) items. See #8160.
|
|
557
|
+
//
|
|
558
|
+
// ■ List item 1. // [listStyle="square", listType="bulleted"]
|
|
559
|
+
// ○ List item 1.1. // [listStyle="circle", listType="bulleted"]
|
|
560
|
+
// ○ [] (selection is here)
|
|
561
|
+
//
|
|
562
|
+
// Then, pasting a list with different attributes (listStyle, listType):
|
|
563
|
+
//
|
|
564
|
+
// 1. First. // [listStyle="decimal", listType="numbered"]
|
|
565
|
+
// 2. Second // [listStyle="decimal", listType="numbered"]
|
|
566
|
+
//
|
|
567
|
+
// The `listType` attribute will be corrected by the `ListEditing` converters.
|
|
568
|
+
// We need to adjust the `listStyle` attribute. Expected structure:
|
|
569
|
+
//
|
|
570
|
+
// ■ List item 1. // [listStyle="square", listType="bulleted"]
|
|
571
|
+
// ○ List item 1.1. // [listStyle="circle", listType="bulleted"]
|
|
572
|
+
// ○ First. // [listStyle="circle", listType="bulleted"]
|
|
573
|
+
// ○ Second // [listStyle="circle", listType="bulleted"]
|
|
574
|
+
const previousSibling = item.previousSibling;
|
|
575
|
+
if (shouldInheritListTypeFromPreviousItem(previousSibling, item, strategy.attributeName)) {
|
|
576
|
+
writer.setAttribute(attributeName, previousSibling.getAttribute(attributeName), item);
|
|
577
|
+
wasFixed = true;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return wasFixed;
|
|
583
|
+
};
|
|
694
584
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if ( baseListAttribute == attributeStrategy.defaultValue ) {
|
|
718
|
-
return false;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
if ( baseItem.getAttribute( 'listType' ) !== itemToChange.getAttribute( 'listType' ) ) {
|
|
722
|
-
return false;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
return true;
|
|
585
|
+
/**
|
|
586
|
+
* Checks whether the `listStyle`, `listReversed` and `listStart` attributes
|
|
587
|
+
* should be copied from the `baseItem` element.
|
|
588
|
+
*
|
|
589
|
+
* The attribute should be copied if the inserted element does not have defined it and
|
|
590
|
+
* the value for the element is other than default in the base element.
|
|
591
|
+
*/
|
|
592
|
+
function shouldInheritListType(baseItem, itemToChange, attributeStrategy) {
|
|
593
|
+
if (!baseItem) {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
const baseListAttribute = baseItem.getAttribute(attributeStrategy.attributeName);
|
|
597
|
+
if (!baseListAttribute) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
if (baseListAttribute == attributeStrategy.defaultValue) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
if (baseItem.getAttribute('listType') !== itemToChange.getAttribute('listType')) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
return true;
|
|
726
607
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
const previousItemListAttribute = previousItem.getAttribute( attributeName );
|
|
753
|
-
|
|
754
|
-
if ( !previousItemListAttribute || previousItemListAttribute === itemToChange.getAttribute( attributeName ) ) {
|
|
755
|
-
return false;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
return true;
|
|
608
|
+
/**
|
|
609
|
+
* Checks whether the `listStyle`, `listReversed` and `listStart` attributes
|
|
610
|
+
* should be copied from previous list item.
|
|
611
|
+
*
|
|
612
|
+
* The attribute should be copied if there's a mismatch of styles of the pasted list into a nested list.
|
|
613
|
+
* Top-level lists are not normalized as we allow side-by-side list of different types.
|
|
614
|
+
*/
|
|
615
|
+
function shouldInheritListTypeFromPreviousItem(previousItem, itemToChange, attributeName) {
|
|
616
|
+
if (!previousItem || !previousItem.is('element', 'listItem')) {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
if (itemToChange.getAttribute('listType') !== previousItem.getAttribute('listType')) {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
const previousItemIndent = previousItem.getAttribute('listIndent');
|
|
623
|
+
if (previousItemIndent < 1 || previousItemIndent !== itemToChange.getAttribute('listIndent')) {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
const previousItemListAttribute = previousItem.getAttribute(attributeName);
|
|
627
|
+
if (!previousItemListAttribute || previousItemListAttribute === itemToChange.getAttribute(attributeName)) {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
return true;
|
|
759
631
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
writer.removeAttribute( 'listStyle', item );
|
|
783
|
-
writer.removeAttribute( 'listReversed', item );
|
|
784
|
-
writer.removeAttribute( 'listStart', item );
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
return true;
|
|
788
|
-
};
|
|
632
|
+
/**
|
|
633
|
+
* Removes the `listStyle`, `listReversed` and `listStart` attributes from "todo" list items.
|
|
634
|
+
*/
|
|
635
|
+
function removeListItemAttributesFromTodoList(editor) {
|
|
636
|
+
return (writer) => {
|
|
637
|
+
const todoListItems = getChangedListItems(editor.model.document.differ.getChanges())
|
|
638
|
+
.filter(item => {
|
|
639
|
+
// Handle the todo lists only. The rest is handled in another post-fixer.
|
|
640
|
+
return item.getAttribute('listType') === 'todo' && (item.hasAttribute('listStyle') ||
|
|
641
|
+
item.hasAttribute('listReversed') ||
|
|
642
|
+
item.hasAttribute('listStart'));
|
|
643
|
+
});
|
|
644
|
+
if (!todoListItems.length) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
for (const item of todoListItems) {
|
|
648
|
+
writer.removeAttribute('listStyle', item);
|
|
649
|
+
writer.removeAttribute('listReversed', item);
|
|
650
|
+
writer.removeAttribute('listStart', item);
|
|
651
|
+
}
|
|
652
|
+
return true;
|
|
653
|
+
};
|
|
789
654
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
} );
|
|
805
|
-
};
|
|
655
|
+
/**
|
|
656
|
+
* Restores the `listStyle` attribute after changing the list type.
|
|
657
|
+
*/
|
|
658
|
+
function restoreDefaultListStyle(editor) {
|
|
659
|
+
return (evt, changedItems) => {
|
|
660
|
+
changedItems = changedItems.filter(item => item.is('element', 'listItem'));
|
|
661
|
+
editor.model.change(writer => {
|
|
662
|
+
for (const item of changedItems) {
|
|
663
|
+
// Remove the attribute. Post-fixer will restore the proper value.
|
|
664
|
+
writer.removeAttribute('listStyle', item);
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
};
|
|
806
668
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
function getChangedListItems(
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
return items;
|
|
669
|
+
/**
|
|
670
|
+
* Returns the `listItem` that was inserted or changed.
|
|
671
|
+
*
|
|
672
|
+
* @param changes The changes list returned by the differ.
|
|
673
|
+
*/
|
|
674
|
+
function getChangedListItems(changes) {
|
|
675
|
+
const items = [];
|
|
676
|
+
for (const change of changes) {
|
|
677
|
+
const item = getItemFromChange(change);
|
|
678
|
+
if (item && item.is('element', 'listItem')) {
|
|
679
|
+
items.push(item);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return items;
|
|
824
683
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
return null;
|
|
684
|
+
function getItemFromChange(change) {
|
|
685
|
+
if (change.type === 'attribute') {
|
|
686
|
+
return change.range.start.nodeAfter;
|
|
687
|
+
}
|
|
688
|
+
if (change.type === 'insert') {
|
|
689
|
+
return change.position.nodeAfter;
|
|
690
|
+
}
|
|
691
|
+
return null;
|
|
836
692
|
}
|
|
837
|
-
|