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